欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

fragment-Android 6.0开发者文档

程序员文章站 2022-05-13 22:09:28
...

原文地址:Fragments

一、前言

fragment可以完成activity的一部分行为或UI。你可以在一个activity中添加多个fragment,以建立一个多模块的UI,另外,你可以在不同的activity中重用这些fragment。你可以把fragment理解为activity的模块化的一部分,它有自己的生命周期,接收自己的事件,在activity运行时可以动态添加或删除(有点像“子activity”,你可以在不同activity重用它)。

fragment必须植入到activity中,它的生命周期是被宿主activity的生命周期完全影响的。例如,当activity进入paused状态,fragment也会进入paused状态,当activity被销毁,fragment也会被销毁。然而,当activity在运行时(在resumed状态),你可以单独的操作某个fragment,例如添加或删除它们。当你对某个fragment进行操作时,你可以将这个操作添加到由activity管理的一个回退栈中(这个回退栈的每个节点都是某个fragment动作的记录)。这个回退栈可以让用户在按下back按钮后撤销fragment动作。

当你将fragment添加到activity的布局中时,它就处于activity视图层的一个ViewGroup中,这时fragment可以定义它自己的视图布局。你可以activity的布局文件中添加<fragment>元素,或在代码中将fragment加入到某个已存在的ViewGroup中,来将fragment添加到activity中。但是,这并不是说fragment必须作为activity布局的一部分,你可以在activity中添加没有UI的fragment去做一些不需要界面的工作。

本文档描述了怎样将fragment运用到你的应用中,包括当fragment被加入到activity回退栈时怎样维持自己的状态、fragment怎样与activity和此activity的其他fragment共享事件等等。


二、设计理念

Android在Android 3.0(API 11)加入fragment功能,最初的目标是在大屏幕上实现更灵活的动态UI设计,如平板上。由于平板的屏幕比手机大很多,也就多出了很多能容纳UI组件的空间。fragment将你从管理复杂的视图层级变化中解救出来。通过将activity的布局分割为多个fragment,你可以在运行时修改activity的界面,并且将这些修改保存到activity管理的回退栈中。

例如,应用可以在界面左边使用一个fragment显示文章列表,在右边显示某个文章的详细信息————这两个fragment处于同一个activity中,一边一个,每个fragment都有自己的生命周期回调方法,并且能分别处理它们自己的输入事件。这样的话,用户就不需要在一个activity选择文章,而另一个activity显示文章了,用户可以在同一个activity中选择文章然后阅读它,就像图1中表现的那样。
fragment-Android 6.0开发者文档

你应该将fragment设计为一个模块化、可重复利用的activity组件。每个fragment都有自己的布局和生命周期回调,你可以在不同的activity中使用这个fragment,因此你应该将fragment设计为可重用的,另外你应该避免在fragment中直接操作另一个fragment。重用性对于fragment很重要,因为你需要用fragment的组合去适应不同的屏幕大小。当你将你的应用设计为既支持平板又支持手机时,你可以在不同的布局配置中重用这些fragment,从而让用户可以在不同的屏幕大小中获得最优的体验。一个典型例子是:在手机上,有必要将平板上一个activity可以放下的多个fragment分解为多个activity。

在图1中,当应用运行在平板上时,可以在activity A中放入两个fragment,而在手机上,没有足够的空间能放下两个fragment,所以activity A只包含文章列表的fragment,当用户选择某个文章时,启动activity B,其包含另一个显示文章信息的fragment。这样的话,应用就通过重用fragment同时支持了平板和手机。


三、创建fragment

fragment-Android 6.0开发者文档

你可以通过继承Fragment或Fragment的子类来创建一个fragment。Fragment类的代码看起来很像Activity类。它的回调方法跟activity的回调方法很像,如onCreate()、onStart()、onPause()、onStop()。事实上,如果你的应用想要针对fragment做重构,你会发现activity的回调方法可以很简单的移植到各个fragment中。

通常,对于fragment你需要至少实现以下生命周期方法:

  • onCreate()
    当创建fragment时系统会调用此方法。在此方法中,你应该做一些必要的初始化工作。

  • onCreateView()
    当fragment第一次绘制用户界面时系统会调用此方法。如果你的fragment要绘制UI,你必须在这个方法返回一个表示fragment布局的View,当然,如果你的fragment不需要UI,你可以返回null。

  • onPause()
    当用户离开此fragment时系统会调用此方法。在这个方法中,你应该保存当前用户会话的所有数据,因为用户可能不会回到此fragment了。

应用至少需要为每个fragment实现以上三个方法,当然,你也可以实现其他几个方法来处理fragment的其他生命周期。其他方法的信息请看“处理Fragment生命周期”部分。

除了Fragment基础类,你也可以继承其他几个Fragment的子类:

  • DialogFragment
    表示一个浮动的对话框。使用这个类创建对话框的好处是可以将此fragment加入到activity管理的fragment回退栈中,从而使用户在fragment关闭的情况下可以重新返回此fragment。

  • ListFragment
    显示一个item的列表(列表数据由adapter管理,如SimpleCursorAdapter),类似ListActivity。它提供了几个管理列表view的方法,例如onListItemClick()方法,可以处理点击事件。

  • PreferenceFragment
    显示一个Preference对象的列表,类似PreferenceActivity。它在创建设置界面时很有用。

3.1 添加用户界面

fragment通常有自己的布局,并作为activity界面的一部分。

要为fragment添加布局,你必须实现onCreateView()方法,当fragment需要绘制布局时系统会调用它。在这个方法中,你必须返回一个表示此fragment布局的View。

注意:如果你的fragment继承了ListFragment,由于ListFragment的默认实现会在onCreateView()方法中返回一个ListView,所以你可以不实现onCreateView()方法。

你可以用XML定义的layout资源填充一个View作为onCreateView()的返回。系统提供LayoutInflater对象帮助你填充这个View。

使用方法如下(下例中Fragment从example_fragment.xml加载布局):

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

onCreateView()的container参数是一个ViewGroup,表示此fragment将要插入的属于activity的父布局。savedInstanceState参数是一个Bundle,其中包含fragment之前保存的数据。

inflate()方法需要三个参数:

  • 你想要填充的布局文件的resource ID
  • 你填充的布局将要插入的ViewGroup。这个参数对于系统很重要,系统会根据它确定填充的布局的根视图的参数(此参数由ViewGroup指定)
  • 一个boolean,表示将填充的布局插入到ViewGroup时是否依附于此ViewGroup。在本例中,这个参数为false,因为系统已经将此填充的布局插入到container中。如果设置为true,将会在最后的布局中创建额外的view group。

3.2 将fragment添加到activity中

通常情况下,fragment都作为宿主activity的UI的一部分,并存在于activity的视图层级中。向activity布局添加fragment有两种方法:

  • 在activity的布局文件中声明fragment
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

<fragment>标签的android:name属性指定了显示布局的fragment类。
系统在创建activity布局时,会实例化布局文件中指定的每个fragment,并调用它们的onCreateView()方法。系统会将此方法返回的View插入到<fragment>标签所在的地方。

注意:每个fragment都需要指定一个id,当activity重启时,系统会用此id恢复fragment(你也可以用此id对fragment做一些操作,如移除fragment)。为fragment添加id有三种方式:

  • 添加android:id属性
  • 添加android:tag属性
  • 如果上述两个数据都没有指定,系统会使用fragment容器的id
  • 在ViewGroup中添加fragment
    你可以在activity运行的任何时候向activity布局添加fragment。你只需要指定fragment将要放置在的ViewGroup即可。

如果要在activity中操作fragment,你需要使用FragmentTransaction的API。你可以用下例中的代码获取FragmentTransaction:

FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

然后你就可以使用add()方法添加fragment了。代码如下:

ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();

add()方法的第一个参数是fragment将要插入到的ViewGroup,由resource ID指定,第二个参数是将要插入的fragment。
每次你对fragment做了操作,必须调用commit()方法。

3.3 添加没有界面的fragment

前面的例子说明了怎样添加有界面的fragment,然而,你也可以为activity添加没有界面的fragment,让它在后台做一些操作。

使用add(Fragment,String)方法可以为activity添加没有界面的fragment(这里的参数为string类型的“tag”,而不是视图ID)。使用这个方法添加fragment是不会收到onCreateView()回调的,所以你不需要实现此方法。

不单单没有界面的fragment可以使用string类型的tag指定,有界面的fragment也可以,只是没有界面的fragment只能用string类型的tag指定而已。如果你想要从activity中获取此没有界面的fragment,你需要调用findFragmentByTag()方法。


四、管理fragment

在activity中调用getFragmentManager()获取FragmentManager,可以管理此activity的fragment。

使用FragmentManager,你可以:

  • 用findFragmentById()或findFragmentByTag()方法获取activity的某个fragment
  • 用popBackStack()方法从回退栈中弹出fragment(模拟用户点击返回键)
  • 用addOnBackStackChangedListener()方法注册一个监听器,以监听回退栈的变化

在前面的部分,我们也提到可以使用FragmentManager开始一个FragmentTransaction,从而进行添加、删除fragment等操作。
更多信息请看FragmentManager类的文档部分。


五、fragment事务

在activity中使用fragment有一个特性:你可以添加、删除、替换或对fragment进行其他操作,来作为对用户动作的响应。每个你提交给activity的fragment变化称为一个事务(transaction),你可以用FragmentTransaction中的API执行这些事务。你可以将事务添加到activity管理的回退栈中,从而允许用户撤销操作。

你可以用下列代码获取FragmentTransaction:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

事务包含了你想要在一次事务中进行的若干操作。你可以用add()、remove()、replace()等方法为指定事务设置操作。然后,你可以用commit()方法将事务提交给activity。

在调用commit()方法之前,你可以调用addToBackStack()方法将事务加入到fragment事务的回退栈中。这个回退栈由activity管理,可以实现用户点击返回键后回到上一个fragment状态的功能。

下例中,描述了怎样替换fragment,并且将状态保存到回退栈中:

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();

在这个例子中,newFragment替换了原来的fragment,并将替换操作保存在回退栈中,当用户点击返回键时,此次替换将会被撤销,之前的fragment会重新显示。

如果你在一次事务中进行了多次操作,然后调用addToBackStack(),那么用户点击返回键后所有的操作都会被撤销。

操作在FragmentTransaction中的顺序不重要,但是注意:

  • 最后调用commit()
  • 如果你在同一个容器中添加了多个fragment,那么这些fragment在视图层级的顺序将取决于你添加的顺序

如果你在事务中移除了某个fragment,但是没有调用addToBackStack(),那么当此次事务被提交后,此fragment会被销毁,用户将不能返回到这个fragment。然而,如果你调用了addToBackStack(),那么此fragment会进入stopped状态,当用户返回时它会重新进入resumed状态。

注意:你可以在commit前调用setTransaction()方法为事务添加过渡动画。

调用commit()方法后事务不会立刻被执行,而是提交到activity的UI线程执行队列中,由线程空闲时执行。你可以在UI现场中调用executePendingTransaction()方法来立刻执行commit()方法提交的事务。通常你不需要这样做,除非在其他线程有工作依赖于此事务。

注意:你只能在用户离开activity前使用commit()提交事务,否则会抛出异常。这是为了防止在activity恢复后丢失提交的状态。如果你可以接受提交失败,那么可以调用commitAllowingStateLoss()方法。


六、与activity通信

虽然fragment的实现不依赖于某个activity,而且可以被用于多个activity中,但是fragment实例仍然跟它的宿主activity紧密的联系在一起。

fragment可以使用getActivity()方法访问宿主activity,从而可以很方便的执行一些任务,如在activity布局中寻找某个view:

View listView = getActivity().findViewById(R.id.list);

同样的,宿主activity可以调用FragmentManager的findFragmentById()或findFragmentByTag()方法获得Fragment实例:

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

6.1 在activity注册事件回调

在某些情况下,你可能需要fragment分享事件给activity。一个好的做法是:fragment定义一个回调接口,然后宿主activity实现它。当activity收到此回调时,它就可以将信息分享给其他fragment了。

例如,如果你的activity有两个fragment,一个显示文章列表(fragment A),另一个显示文章信息(fragment B)。那么fragment A必须通知activity某篇文章被选中了,这样activity才能通知fragment B显示此文章。在下面的例子中,fragment A声明了一个OnArticleSelectedListener接口:

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

然后activity实现OnArticleSelectedListener接口,并override其onArticleSelected方法,在此方法中activity将fragment A的信息通知给fragment B。为了确保宿主activity实现了此接口,fragment A可以在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 A会抛出ClassCastException。如果activity实现了此接口,那么fragment A就可以通过调用OnArticleSelectedListener的onArticleSelected方法向activity分享事件了。本例中,fragment A继承了ListFragment,每当用户点击了某个item,那么系统会通过onListItemClick()通知fragment A,然后fragment A调用onArticleSelected方法分享事件给activity:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);
    }
    ...
}

6.2 为action bar添加item

你的fragment可以通过实现onCreateOptionsMenu()方法为activity的菜单(options menu)添加选项。为了让此方法被调用,你必须在onCreate()方法中调用setHasOptionsMenu(),来表明你的fragment会向菜单添加选项(否则你的fragment的onCreateOptionsMenu()方法不会被调用)。

你通过fragment添加的选择会被加入到已经存在的菜单中。当它添加的选择被点击时,fragment会通过onOptionsItemSelected()方法获取回调。

你也可以通过调用registerForContextMenu()方法将fragment布局中的view添加到系统菜单(context menu)中。当用户打开系统菜单时,fragment会在onCreateContextMenu()方法获取回调。当用户选择了某个选项时,fragment可以通过onContextItemSelected()方法获得回调。

注意:当用户点击菜单时,虽然你的fragment可以获取相关回调,但是activity会更早的获取回调。只有activity的回调没有处理此事件,你的fragment才会被通知


七、处理fragment生命周期

fragment-Android 6.0开发者文档

管理fragment的生命周期跟管理activity的生命周期类似。fragment也存在三个状态:

  • resumed。表示此fragment在当前activity是可见的
  • paused。表示另一个activity在前台且拥有焦点,而fragment所在的activity仍然可见(前台activity部分透明或没有占满整个屏幕)
  • stopped。表示此fragment不可见。可能是宿主activity被停止或者fragment被移除出activity并加入到回退栈中。处于stopped状态的fragment仍然存活,它的状态和信息仍然被系统保存着。然而此fragment已经对用户不可见,而且当activity被kill的时候它也会被kill

类似于activity,你可以用bundle保存fragment状态,用于activity进程被kill后重新创建时恢复fragment状态。你可以在fragment的onSaveInstanceState()方法保存状态,并在onCreate()方法、onCreateView()方法、onActivityCreated()方法恢复。

activity与fragment生命周期的不同主要体现在回退栈上。默认情况下,当activity被stopped的时候,会被压入系统管理的activity回退栈中。而fragment只有在移除fragment的事务中,主动要求加入回退栈(调用addToBackStack()方法),才会被压入宿主activity管理的回退栈中。

此外,管理fragment生命周期很像管理activity生命周期。你可以参考activity的生命周期管理文档。你真正需要在这个文档了解的,是activity的生命周期怎样影响fragment的生命周期。

注意:如果你的fragment需要Context对象,可以调用getActivity()方法。然而,只有当fragment已经attach了activity才能调用此方法。如果fragment没有attach了activity,或者已经detached,那么getActivity()会返回null

7.1 fragment与activity的生命周期关系

activity的生命周期会影响fragment的生命周期。例如,当activity调用onPause()时,每个fragment都会调用onPause()。

fragment有一些特有的生命周期回调方法,以处理与activity的独特的交互,比如创建或销毁fragment的UI。这些特有的回调方法包括:

  • onAttach()。当fragment与activity绑定的时候会调用
  • onCreateView()。创建fragment的界面时调用
  • onActivityCreated()。当activity的onCreate()方法返回后会调用
  • onDestroyView()。当fragment的界面被移除时调用
  • onDetach()。当fragment与activity解绑的时候调用

activity的生命周期与fragment的生命周期的关系,已经在图3中说明了。

activity进入resumed状态后,你可以*的向此activity添加或移除fragment。因此,只有当activity进入resumed状态,fragment的生命周期变化才是独立的。

当activity退出resumed状态,fragment的生命周期就再次跟activity结合在一起。

相关标签: fragment