本节例程下载地址:WillFlowFragment
究竟要如何使用碎片才能充分地利用平板屏幕的空间呢?假如我们正在开发一个新闻应用,其中一个界面使用 ListView 展示了一组新闻的标题,当点击了其中一个标题,就打开另一个界面显示新闻的详细内容。如果是在手机中设计,我们可以将新闻标题列表放在一个活动中,将新闻的详细内容放在另一个活动中。
这是使用Fragment最简单的一种方式,把Fragment当成普通的控件,直接写在Activity的布局文件中就可以了。
1、继承Fragment,重写onCreateView决定Fragemnt的布局
2、在Activity中声明此Fragment,就当和普通的View一样
下面我们用一个例子来展示静态创建 Fragment 的过程。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button"
android:layout_marginTop="96dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
RelativeLayout>
这个布局非常简单,只放置了一个按钮,并让它水平居中显示。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fc6243"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="#4352fc"
android:text="这个是右侧的Fragment"
android:textSize="22sp"
android:layout_marginTop="96dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:id="@+id/textView" />
<ImageView
android:id="@+id/imageView"
android:layout_width="236dp"
android:layout_height="236dp"
android:layout_marginTop="40dp"
app:srcCompat="@drawable/willflow"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true" />
RelativeLayout>
可以看到,我们将这个布局的背景色设置成浅红色,并放置了一个 TextView 用于显示一段文本。
接着新建一个 LeftFragment 类,继承自 Fragment,然后在关键的生命周期方法中插入代码(就和在处理 Activity 时一样)。注意:创建 Fragment 时,必须重写 onCreateView() 回调方法来定义布局。事实上,这是唯一一个为使 Fragment 运行起来需要重写的回调方法。
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.left_fragment, container, false);
return view;
}
}
这里仅仅是重写了 Fragment 的 onCreateView()方法,然后在这个方法中通过 LayoutInflater的 inflate()方法将刚才定义的 left_fragment 布局动态加载进来,整个方法再简单明不过了。
public class RightFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.right_fragment, container, false);
return view;
}
}
基本上代码都是相同的,相信已经没有必要再做什么解释了。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/left_fragment"
android:name="com.wgh.willflowfragment.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/right_fragment"
android:name="com.wgh.willflowfragment.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
LinearLayout>
可以看到,我们使用了标签在布局中添加碎片,其中指定的大多数属性我们都是熟悉的,只不过这里还需要通过 android:name 属性来显式指明要添加的碎片类名,注意一定要将类的包名也加上。
注意: 当通过 XML 布局文件的方式将 Fragment 添加进 Activity 时,Fragment 是不能被动态移除的。如果想要在用户交互的时候把 Fragment 切入与切出,必须在 Activity 启动后,再将 Fragment 添加进 Activity,我们接下来学习这种方法。
在刚才的例子当中,我们已经学会了在布局文件中添加Fragment的方法,不过Fragment真正的强大之处在于,它可以在程序运行时动态地添加到Activity当中。根据具体情况来动态地添加Fragment,我们就可以将程序界面定制得更加多样化。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffaa00"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_gravity="center_horizontal"
android:layout_marginTop="33dp"
android:text="这个是另一个右侧的Fragment"
android:textColor="#cb43fc"
android:textSize="20sp" />
<ImageView
android:id="@+id/imageView"
android:layout_width="136dp"
android:layout_height="136dp"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true"
android:layout_marginTop="24dp"
android:src="@mipmap/ic_launcher" />
RelativeLayout>
这个布局文件的代码和 right_fragment.xml 中的代码基本相同,只是将背景色改成了橘黄色,并将显示的文字改了改。
public class AnotherRightFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.another_right_fragment, container, false);
return view;
}
}
代码同样非常简单,在 onCreateView() 方法中加载了刚刚创建的 another_right_fragment 布局。这样我们就准备好了另一个Fragment,接下来看一下如何将它动态地添加到活动当中。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/left_fragment"
android:name="com.wgh.willflowfragment.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/right_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<fragment
android:id="@+id/right_fragment"
android:name="com.wgh.willflowfragment.AnotherRightFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
FrameLayout>
LinearLayout>
可以看到,现在将右侧Fragment放在了一个 FrameLayout 中,还记得这个布局吗?在之前我们学过,这是 Android 中最简单的一种布局,它没有任何的定位方式,所有的控件都会摆放在布局的左上角。由于这里仅需要在布局里放入一个Fragment ,因此非常适合使用 FrameLayout 帧布局。之后我们将在代码中替换 FrameLayout 里的内容,从而实现动态添加Fragment 的功能。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button_lf);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AnotherRightFragment fragment = new AnotherRightFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
transaction.commit();
}
});
}
可以看到,首先我们给左侧Fragment 中的按钮注册了一个点击事件,然后将动态添加Fragment 的逻辑都放在了点击事件里进行。
尽管 Fragment 是作为独立于 Activity 的对象实现,并且可在多个 Activity 内使用,但 Fragment 的给定实例会直接绑定到包含它的 Activity。
具体地说,Fragment 可以通过 getActivity() 访问 Activity 实例,并轻松地执行在 Activity 布局中查找视图等任务。
View listView = getActivity().findViewById(R.id.listView);
同样地,我们的 Activity 也可以使用 findFragmentById() 或 findFragmentByTag(),通过从 FragmentManager 获取对 Fragment 的引用来调用Fragment中的方法。例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
在某些情况下,我们可能需要通过 Fragment 与 Activity 共享事件。执行此操作的一个好方法是,在Fragment内定义一个回调接口,并要求宿主 Activity 实现它。 当 Activity 通过该接口收到回调时,可以根据需要与布局中的其他片段共享这些信息。
例如,如果一个新闻应用的 Activity 有两个Fragment:一个用于显示文章列表(Fragment A),另一个用于显示文章(Fragment B)— 那么Fragment A 必须在列表项被选定后告知 Activity,以便它告知Fragment B 显示该文章。
在下面的例子中,OnArticleSelectedListener 接口在 FragmentA 内声明:
public static class FragmentA extends ListFragment {
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
}
然后,该Fragment的宿主 Activity 会实现 OnArticleSelectedListener 接口并替代 onArticleSelected(),将来自Fragment A 的事件通知Fragment B。为确保宿主 Activity 实现此接口,Fragment A 的 onAttach() 回调方法(系统在向 Activity 添加Fragment时调用的方法)会通过转换传递到 onAttach() 中的 Activity 来实例化 OnArticleSelectedListener 的实例:
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
}
如果 Activity 未实现接口,则Fragment会引发 ClassCastException。实现时,mListener 成员会保留对 Activity 的 OnArticleSelectedListener 实现的引用,以便Fragment A 可以通过调用 OnArticleSelectedListener 接口定义的方法与 Activity 共享事件。例如,如果Fragment A 是ListFragment的一个扩展,则用户每次点击列表项时,系统都会调用片段中的 onListItemClick(),然后该方法会调用 onArticleSelected() 以与 Activity 共享事件:
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
mListener.onArticleSelected(noteUri);
}
}
传递到 onListItemClick()的 id 参数是被点击项的行 ID,即 Activity(或其他片段)用来从应用的 ContentProvider获取文章的 ID。
为了重用 Fragment UI 组件,我们应该把每个 Fragment 都构建成完全自包含的、模块化的组件,即定义它们自己的布局与行为。一旦我们定义了这些可重用的 Fragment,我们就可以通过应用程序逻辑让它们关联到 Activity,以实现整体的复合 UI。
通常 Fragment 之间可能会需要交互,比如基于用户事件的内容变更。所有 Fragment 之间的交互应通过与之关联的 Activity 来完成,也就是说两个 Fragment 之间不应直接交互。
为了让 Fragment 与包含它的 Activity 进行交互,可以在 Fragment 类中定义一个接口,并在 Activity 中实现。该 Fragment 在它的 onAttach() 方法生命周期中获取该接口的实现,然后调用接口的方法,以便与 Activity 进行交互。
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// 容器 Activity 必须实现该接口
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// 确认容器 Activity 已实现该回调接口。否则,抛出异常
try {
mCallback = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " 必须实现这个接口!");
}
}
}
现在 Fragment 可以通过调用 mCallback(OnHeadlineSelectedListener 接口的实例)的 onArticleSelected() 方法(也可以是其它方法)与 Activity 进行消息传递。
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// 向宿主 Activity 传送事件
mCallback.onArticleSelected(position);
}
##### (2)实现接口
为了接收回调事件,宿主 Activity 必须实现在 Fragment 中定义的接口。
例如,下面的 Activity 实现了上面例子中的接口:
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{
public void onArticleSelected(int position) {
// 在这里做点什么...
}
}
宿主 Activity 通过 findFragmentById() 获取Fragment 的实例,然后直接调用 Fragment 的 public 方法向 Fragment 传递消息。
例如,假设上面所示的 Activity 可能包含另一个 Fragment,该 Fragment 用于展示从上面的回调方法中返回的指定的数据。在这种情况下,Activity 可以把从回调方法中接收到的信息传递到这个展示数据的 Fragment。
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{
public void onArticleSelected(int position) {
ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment);
if (articleFrag != null) {
articleFrag.updateArticleView(position);
} else {
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
}
这里面涉及到了返回栈的概念,我们接下来给大家简单介绍下。
上面的代码中我们成功实现了向Activity中动态添加Fragment的功能,不过通过点击按钮添加了一个Fragment之后,这时按下 Back 键程序就会直接退出而不是返回。如果这里我
们想模仿类似于返回栈的效果,就需要将一个事务添加到返回栈中。修改 MainActivity 中的代码如下即可:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button_lf);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AnotherRightFragment fragment = new AnotherRightFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
});
}
联系方式:
简书:WillFlow
CSDN:WillFlow
微信公众号:WillFlow