使用Fragment创建应用

负责人:sfshine

转载连接:http://wiki.eoe.cn/page/Fragments

原文链接:http://developer.android.com/guide/topics/fundamentals/fragments.html

一个**碎片**在一个活动中代表一个行为或用户界面的一部分。 你可以在一个单一的活动中组合使用多个碎片以建立一个多窗格的UI,并且可以在多个活动中重用一个碎片。你可以认为是一个拥有独立生命周期、能够独立接受输入事件、并且可以在活动运行时添加或移除的碎片作为一个活动的模块化部分(有点像一个你可以在不同活动中重用的子活动)。

在一个Activity中,一个Fragment代表一个用户接口的行为或者部分行为.你可以把几个Fragment混合到一个Activity中,这样你就可以创建一个多个页面的UI并可以在多个Activity中复用一个Fragment.

一个碎片必须总是嵌入到一个活动(activity)中,并且它的生命周期直接受到住活动得生命周期的影响。例如:当活动暂停或销毁时时,它里边的所有碎片也是如此。然而,当一个活动运行时,(它在 resumed(重新开始)**生命周期状态**,你可以单独的操作每个碎片,例如添加或移除它们。当你执行了这样的碎片事务,你也可以将它添加到一个后台堆栈所管理的活动——该活动中的每个后台堆栈条目都记录着已发生的片段事务。后台堆栈允许用户通过按返回按钮退一个碎片事务(向后导航)。

当你增加一个Fragment来作为Activity界面布局的一部分时,他存在在Activity视图层里面的一个ViewGroup中,这个Fragment定义了自己的视图.你可以通过在Activity的布局文件中用声明这个fragment来把这个fragment插入你的activity中,或者你可以在应用的代码中把他添加添加到一个现存的ViewGroup中.然而,Fragment不必是一个activity布局的一部分,你可以使用一个Fragment而不实用他的UI,这样可以让Fragment作为Activity的一个不可见部分来工作.

这个文档介绍了增加使用Fragment创建你的应用,包括怎样使fragment在增加到返回栈的时候保持他们的状态,和activity以及该activity的其他fragment的共享事件,组成Activity的ActionBar,等等.

Design Philosophy-设计理念


Android在Android3.0(API等级11)中引入了Fragment,主要是为了在大屏幕上(比如平板)支持更多动态的灵活的UI设计.因为平板的屏幕比其他手持设备大多了,有更多的空间来组合,交换UI组件.Fragment使你在View层不必进行很复杂变化就可以就可以实现这些设计.通过把Activity的布局分解成很多Fragment,你可以在运行时改动activity的界面并且可以把这些变化保存在activity管理的返回栈中.

比如,一个新闻应用可以使用一个Fragment在左边显示一列文章标题而在左边的另一个Fragment显示文章详细内容.这两个Fragment都在同一个Activity中,他们并排着,每个Fragment有他自己的生命周期回调方法,处理他们各自的输入事件.那么,不需要在一个activity中选择在另一个activity中阅读,用户可以选择一篇文章在同一个activity中阅读这个新闻的内容.如图1所示:

你应该把fragment设计成模块化的,可复用的Activity组件.就是说,每个Fragment定义了他自己的布局和他自己的,拥有自己生命周期回调的行为,你可以在多个activity中包含一个Fragment,所以你应该把Fragment设计成可以复用的,并且需要避免从一个fragment直接操纵另一个fragment.这一点是非常重要的因为一个fragment模块允许你针对不同的屏幕尺寸变化你的fragment组合形式.在设计应用来支持平板和手持设备时,你可以在不同的布局配置中重用你的fragment来针对屏幕空间优化用户体验.比如,在一个手持设备上,可能需要分开的fragment来提供一个单独窗口UI而不是使很多fragment在同一个activity中放不开.

图例1 平板上,在一个activity中Fragment怎么定义两个UI模块,但是在手持设备上他们将分开.

继续以上面的新闻为例,当在平板大小的设备上运行的时候,这个应用可以在Activity中嵌入两个Fragment.然而,在手持设备上的时候,由于没有足够的空间盛放这两个fragment,所以ActivityA只显示了其中的一个Fragment(新闻列表),当用户选择新闻标题的时候,他跳转到ActvityB,ActivityB中显示第二个Fragment(新闻详细信息).那么通过复用不同组合的Fragment这个应用就可以同时支持平板和手持设备了,如图1.

更多关于使用不同的Fragment组合来设计适应不同屏幕应用的信息,请参阅Supporting Tablets and Handsets一章.

Creating a Fragment-创建一个Fragment


创建一个Fragment,你应该创建一个Fragment的子类(或者他的一个现有子类).Fragment类的代码很像Activity.它还有和activity相似的回调方法,比如onCreate(), onStart(), onPause(), 和 onStop().实际上,如果你在使用Fragment来转换一个现成的应用,你可能只是简单的从你的activity回调方法中移动代码到fragment相应的回调方法中.

一般的,你至少应该实现下面的生命周期方法:

  • onCreate()

    • 创建fragment的时候,系统会调用这个方法.在你实现过程中,当fragment暂停(pause),停止(stop)然后恢复(resume)时,你应该初始化你想要保持的,fragment的必要的组件.
  • onCreateView()

    • 在fragment第一次绘制他的用户界面的时候系统会调用这个方法.如果你想为你的fragment绘制界面,你必须从这个方法中返回一个View,这个View是你fragment布局的基础.如果这个Fragment不提供UI,你可以返回空.
  • onPause()

    • 系统调用这个方法作为用户离开这个fragment的第一标志(虽然这不总是意味着这个Fragment被摧毁了).通常是你需要做一些改变,这些改变超出了当前的用户会话(因为用户有可能不会回到这个界面来).

大多数应用应该至少为每个fragment实现上述的三个方法,但还有些回调函数你需要去实现,用来处理fragment生命周期中的不同状态。所有有关fragment生命周期的回调函数稍后将会在章节Handling the Fragment Lifecycle中讨论

也有有些子类(不是基本的Fragment类)你可能想要继承来实现Fragment:

  • DialogFragment

    • 显示一个浮动的对话框.使用这个类来创建一个对话框是和使用对话Helper方法在Activity类中创建对话框都是很好的方法,因为你可以把Fragment对话框包含在activity管理的Fragment返回栈中,允许用户返回到关闭的Fragment中.
  • ListFragment

    • 展示一列被adapter(比如SimpleCursorAdapter)管理的项,和ListActivity很相似.它提供了一些管理一个列表视图的方法,比如处理点击事件的onListItemClick()方法.
  • PreferenceFragment

    • 用一个列表来显示一组偏好设置对象,类似于PreferenceActivity. 在创建设置型的activity时会用到.

图2 Fragment的生命周期(Activity 正在运行时).

Adding a user interface-添加一个用户接口

一个Fragment经常被用作activity界面的一部分,为activity贡献自己的界面.

为了给fragment提供一个布局,你必须实现onCreateView()方法,Android系统在Fragment绘制他的界面的时候调用这个方法.你对这个方法的实现必须返回一个View,这个View是你Fragment布局的基础.

注意:如果你的Fragment是一个ListFragment类的子类,默认会从onCreateView()返回一个Listview,所以你不需要实现它.

为了从onCreateView()方法返回一个布局,你可以用一个xml布局文件来填充它.为了帮助你做这个事情,onCreateView() 方法提供了一个LayoutInflater对象.

比如,这个一个Fragment的子类,他是从example_fragment.xml文件载入的布局:

1
2
3
4
5
6
7
8
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);
    }
}

Creating a layout-创建一个布局


和上面差不多,R.layout.example_fragment是在系统保存的example_fragment.xml这个布局资源的引用.更多关于使用一个xml文件创建一个布局的信息,参考User Interface 文档.

传递给onCreateView()的容器参数是fragment锁插入的activity的父ViewGroup(来自对应的activity布局).savedInstanceState的参数是一个提供关于之前Fragment状态数据的Bundle,如果这个Fragment被恢复了(resume,恢复数据在处理Fragment生命周期这一节有更多介绍)

inflate()方法接收三个参数:

    • 你想要添加的layout的资源ID.
    • 将作为填充布局的父容器的ViewGroup.传递容器参数是非常重要的,只用这样才能使系统应用布局参数到填充视图的根视图,从而被它的父视图所确定.
    • 一个boolean类型的参数,用于在填充时指明填充的布局是否应该附加在ViewGroup(第二个参数)上.(如果系统已经插入这个填充布局到容器了就返回false,如果将要在最终布局中创建一个多余的viewgroup,那就返回true)

现在你已经知道如何创建一个有布局的fragment,接下来,你需要将fragment加到你的activity中

Adding a fragment to an activity-给一个Activity添加一个Fragment


一般的一个fragment提供了Activity UI的一部分,他作为Activity全局视图层的一部分而嵌入.有两种方法可以把fragment嵌入到Activity布局中:

  • * Declare the fragment inside the activity's layout file-在Activity布局文件中声明Fragment.*

这样的话,你可以把Fragment当作一个视图,比如,这是一个嵌入两个Fragmet的Activity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?xml version"utf-8"?>
<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>

的android:name属性指定了在布局中要实例化的Fragment类.

在系统创建这个Activity布局的时候,他会实例化每个布局中的每个fragment,调用每个fragment的onCreateView()方法来取回每个fragment的视图.系统把fragment返回的视图直接插入到标签所在的地方.

注意.每个fragment需要一个独一无二的id以便系统可以在activity restartt时恢复fragment(你也可以用它来进行fragment的事务处理,例如将它删除)。有三种方法为一个fragment提供id

  • 使用android:id来指定它唯一的ID.

  • 使用android:tag来指定一个唯一的字符串标志

  • 如果上面两个你都不指定,系统会使用容器视图的ID.

  • 或者,机械性的把fragment添加到ViewGroup中.

在任何你Activity运行的时候,你都可以把fragment添加到Activity的视图中.你只需要指定一个用于盛放Fragment的ViewGroup.为了让fragment可以被管理(比如添加,删除,替换fragment),你必须使用来自FragmentTransaction的API.你可以像下面这样在Activity中获取一个FragmentTransaction的实例:

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

你可以使用Add()方法添加一个Fragment,指定要添加的Fragment和目标View,如下:

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

add()方法中的第一个参数是Fragment所要放置的目标ViewGroup,通过资源ID指定,第二个参数是要添加的Fragment.只要你使用FragmentTransaction做了修改,你必须调用commit()方法来使修改生效.

Adding a fragment without a UI-添加一个没有UI的Fragment


上面的例子想你展示了怎么添加一个含有UI的Fragment到你的Actvity.然而,对于不想增加而外UI的Activity来说,你也可以使用Fragment来进行后台行为.

为了添加一个没有UI的Fragment.需要使用add(Fragment, String) 方法,其中,你需要为Fragment提供一个字符串的标志而不是一个视图ID.这样增加的Fragment,由于没有涉及到Activity的视图,所以不会调用onCreateView()方法.所以你不需要实现这个方法.

为Fragment提供一个字符串标志不一定只局限于没有UI的Fragment,你也可以为有UI的Fragment指定一个字符串标志,但是如果这个Fragment真的没有UI,那这个字符串标志是确定它的唯一标志.如果你想在后面从Activity中获取到这个fragment,你需要使用findFragmentByTag()方法.

在FragmentRetainInstance.java文件的例子展示了Actvity怎么使用一个没有UI的fragment来出来后台工作.

Managing Fragments-管理fragment


为了管理你Activity中的fragment,你需要使用FragmentManager.你可以通过你Activity中的getFragmentManager()来获取它.

使用FragmentManager你可以:

  • 使用findFragmentById()(提供UI的Fragment)或者findFragmentByTag()(没有提供UI的Fragment) 获取你Activity存在的Fragment,

  • 使用popBackStack()把Fragment从返回栈中弹出(模拟用户的返回命令).

  • 使用addOnBackStackChangedListener()方法为返回栈的变化注册监听器.

请参考文档的 FragmentManager 类来查看过于这些方法(还有其他方法)的更多内容.

正如前面的文档所讲的,你也可以使用FragmentManager来打开FragmentTransaction,FragmentTransaction允许你执行添加,删除Fragment的事务.

Performing Fragment Transactions-执行Fragment事务


在你的Activity中使用Fragment的最大好处就是可以针对用户的操作,进行对Fragment的添加,移除,替换等等其他操作.你提交给Activity的每个变化称为一个事务,这些事务你可以使用FragmenTransaction的API来实现.你也可以在Activity管理的返回栈中保存每个事务,使用户可以在Fragmen的变化后返回之前的状态(类似于在Activity跳转后的返回).

你可以像这样从FragmentManager中取得一个FragmentTransaction的实例:

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

每个事务是一系列你想要同时执行的Fragmen的变化.你可以使用像add(),remove(),replace()这样的方法来为一个事务设定你想要执行的操作.为了使Activity的事务生效,你必须执行commit()方法.

在你调用commit()方法的之前,为了添加这个事务到一个Fragmen事务的返回栈,你可能想要调用addToBackStack()方法.这个返回栈被Activity管理,允许用户通过按下返回按键返回之前的Fragmen状态.

这里展示了怎么使用一个Fragmen替换另一个,然后在返回栈中返回到之前的状态.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Create new fragment and transaction
//创建一个新的Fragmen和事务
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
//使用这个Fragment替换在Fragmen容器中的Fragmet
// and add the transaction to the back stack
//添加这个事务到返回栈
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
//提交这个事务.
transaction.commit();

在这个例子中,新的Fragmen替换了R.id.fragment_container ID指定的布局容器中当前存在的fragment(如果存在的话).通过调用addToBackStack()方法,替换事务被保存在了返回栈中,这样用户可以回退这个事务,通过按下返回键返回到以前的fragment.

如果你在事务中添加了多个变化(比如另一个add()方法或者remove()方法),然后调用了addToBackStack()方法,那在你调用commit()方法之前的所有变化都会作为单独的事务被添加到返回栈中,返回键将会把他们全部回退.

除了下面这些,其他的情况和你在FragmentTransaction中添加的顺序没有关系

  • 你不行在最后调用commit()方法

  • 如果你在向同一个容器添加多个fragment,那么你添加的顺序决定了他们在视图层出现的顺序.

如果在你执行一个移除所有fragment的事务的时候没有调用addToBackStack()方法,那么这个fragment将会在事务提交后被摧毁,用户不能再返回到之前的fragment.如果你在移除fragment的时候调用了addToBackStack()方法,那这个fragment会被停止,并可以在用户按返回的时候恢复.

小贴士:对于每个fragment事务,你可以在提交之前通过调用setTransition()来应用一个fragment动画.

调用commit()方法不能立即执行事务而是安排它运行在Activity的UI线程中("主"线程)---如果这线程可以这么做的话.如果需要,你可以在你UI线程中调用executePendingTransactions()方法来直接执行commit()方法提交的事务.这么多一般不必要除非事务依赖于其他线程的工作.

注意:你只可以在Activity保存他状态之前(在用户离开这个Actvity的时候)使用commit()方法来提交一个事务.如果你在这个时间点之后提交,系统会抛出一个异常.这是因为如果Activity需要恢复,在提交之后的状态可能会丢失.对于允许丢失提交的情况,请使用commitAllowingStateLoss()方法.

Communicating with the Activity-与Activity的通讯


即使fragment是作为一个object实现的,独立于Activity的并且可以在那多个Activity中使用,但是一个fragment实例还是和它所在的容器有直接的关系.

特别的,fragment可以通过getActivity()方法来访问Activity实例并可以轻易的执行像在activity视图中查找View的任务.

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

Likewise, your activity can call methods in the fragment by acquiring a reference to the Fragment from FragmentManager, using findFragmentById() or findFragmentByTag(). For example:

同样的,使用findFragmentById()或findFragmentByTag()通过从FragmentManager获取一个对这个Fragment的引用,你的Activity可以调用fragment中的方法

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

Creating event callbacks to the activity-为Activity创建时间回调


在一些情况下,你可惜需要一个Fragment和Activity共享事件.一个好的方法是在Fragment中定义一个回调接口然后让承载他的Activity实现它.当Activity通过接口接收到调用时,必要时他可以和视图中的其他Fragment共享信息.

举个例子,如果一个新的应用在一个Activity中有两个Fragment,一个显示一列文章标题(FragmentA),另一列显示文章内容(FragmentB),那么在一列被选中的时候,FragmentA必须告诉Actvity那一列被选中了,这样Actvity就可以告诉FragmentB显示哪一篇文章.在这种情况下,OnArticleSelectedListener 接口会在FragmentA中声明.

1
2
3
4
5
6
7
8
public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

然后承载Fragment的Activity实现OnArticleSelectedListener接口并重写onArticleSelected()方法来通知FragmentB响应FragmentA的事件.为了保证这个Activity实现了这个接口,FragmentA的onAttach()方法(系统在添加Fragment到这个Activity的时候调用)通过把Activity参数传递到onAttach()方法传递实例化一个OnArticleSelectedListener实例.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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的引用.这样FragmentA可以通过调用OnArticleSelectedListener接口定义的方法来共享事件.比如:如果FragmentA是listFragment的扩展,用户每次点击list的一项,系统会调用Fragment的onListItemClick()方法,然后调用onArticleSelected() 方法来和Activity分享事件信息.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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);
    }
    ...
}

onListItemClick()方法传递的参数是点击项的行ID,Activity(或Fragment)可以用它来从应用的ContentProvider填充文章信息.

更多关于使用content provider的信息请参阅Content Providers文档.

Adding items to the Action Bar-在Action Bar上添加项


你的Fragment可以通过实现onCreateOptionsMenu()来为Activity的Options Menu创建菜单项(结果就是形成ActionBar).为了让这个方法接收到调用,你必须在onCreate()方法中调用setHasOptionsMenu()方法来表明这个Fragment允许在Options Menu中增加项(否则,Fragment将不能接收onCreateOptionsMenu()的调用).

你从Fragment 添加到 Options Menu的任何项都是现存菜单项的附加项.在一个菜单项选中的时候,Fragment也接收响应onOptionsItemSelected()方法的调用.

你也可以在你的Fragment视图中通过调用registerForContextMenu()方法来注册一个视图,从而提供一个上下文菜单.当用户打开上下文菜单时,Fragment会接收一个onCreateContextMenu()的调用,当用户选择一项的时候,Fragment接收一个onContextItemSelected()的调用.

注意.即使你的Fragment在每个添加的菜单项接收了一个on-item-selected调用,在用户选择一个菜单项的时候,Activity是第一个接受各自调用的组件.如果Activity实现的on-item-selected调用没有处理选择项后的事件,那这个事件会传递到Fragment的回调中.这对 Options Menu 和上下文菜单都是适用的.

更多关于菜单的信息,参考Menus and Action Bar一文.

Handling the Fragment Lifecycle-处理Fragment的生命周期


Figure 3. The effect of the activity lifecycle on the fragment lifecycle.
图三 Activity生命周期做Fragment生命周期的影响

处理Fragment的生命周期和处理Activity的生命周期很相似.和Activity一样,Fragment的生命周期有一下三个状态:

Resumed

  • * Fragment在运行中的Activity中可见

Paused

  • * 另一个Activity在前台或者获得了焦点,但是Fragment所在的Activity仍然可以看到(可能是前台Activity占据了屏幕的一部分或者是半透明的)

Stopped

  • * Fragment不可见.宿主Activity可能已经被停止了或者这个Fragment已经从这个Activity中移除了并被添加到了返回栈.一个停止的Fragment仍然是存活的(所有的状态和成员信息被系统保存着).然而他不再对Activity可见,如果宿主Activity被杀死了,他也会被杀死.

和Activity一样,在这个Activity所在的进程被杀死或者你需要在Activity重新创建的时候保存Fragment的状态,你可以用Bundle来做这个工作.你可以在Fragment执行onSaveInstanceState()方法的时候保存它的状态,然后在onCreate()或者onCreateView(),onActivityCreated()方法的时候恢复这些状态.更多关于保存状态的内容参考Activity文档.

Activity和Fragment最大的不同是他们在返回栈中的存在形式.默认的,Activity在停止的时候,是放在一个被系统管理的返回栈中(这样用户可以使用back按钮返回,就像在Tasks and Back Stack一章中谈论的那样).然而在一个移除Fragment的事务中,只有在你通过调用addToBackStack()明确的指明这个Fragment不要被保存,这个Fragment才会被放在被宿主Activity管理的返回栈中.

另外,管理Fragment的生命周期和管理Activity的生命周期很相似.所以,当管理Activity生命周期的方法也适于管理Fragment的生命周期.当然你也需要明确Activity对Fragment生命周期的影响.

注意,如果在你的Fragment中需要一个context对象,你可以调用getActivity.然而,只有这个Fragment附在这个Activity上的时候,才可以调用getActivity().如果Fragment还没有附加在Activity上,或者在最后的生命周期和Activity分离了,那getActivity()方法将会返回null.

Coordinating with the activity lifecycle-和Activity生命周期的协调


拥有Fragment的Activity的生命周期会直接影响Fragment的生命周期,每个Activity生命周期方法会影响到每个Fragment.举个例子,当一个Activity执行onPause()方法的时候,它里面的每个Fragment也会执行onPause().

Fragment有一些额外的生命周期,用来处理和Activity的特殊交换,从而可以执行形如创建和销毁FragmentUI的事情.这些额外的回调方法有:

onAttach()

  • * 当Fragment和Activity链接起来的时候调用(Activity在这里传送过来).

onCreateView()

  • * 创建Fragment的视图层.

onActivityCreated()

  • * 当Activity的onCreate返回的时候执行.

onDestroyView()

  • * 当Fragment的试图层被移除的时候执行.

onDetach()

  • * 当Fragment和Activity分离的时候执行.

Fragment生命周期的流图,由于被宿主Activity影响,可以用图三表示.在这个表中,你可以知道每个Activity的每个状态是怎样决定一个Fragment收到的回调方法的.比如,当一个Activity收到他的onCreate()方法的时候,他里吗的Fragment不会再收到onActivityCreated()方法的回调.

一旦Activity到达了resume状态,你可以随意添加和移除Activity中的Fragment.当然,只有这个Activity在resume状态的时候,Fragment的生命周期才可以独立的变化.

然而,当activity离开了resume状态,Fragment会再一次被activity推到它的生命周期中.

Example-例子


为了把上面介绍的知识汇总,这里有个使用两个Fragment组成两个视图布局的例子.下面的activity包含两个Fragment,一个用来显示Shakespeare话剧的标题,另一个用来显示选中话剧的简介.也演示了怎么根据屏幕的不同为这两个Fragment提供不同的配置.

注意:完整代码在FragmentLayout.java中.

主activity用平常的方式生成布局,在onCreate()方法的时候:

1
2
3
4
5
6
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.fragment_layout);
}

The layout applied is fragment_layout.xml:
fragment_layout.xml如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width"match_parent">

    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id"1"
            android:layout_width"match_parent" />

    <FrameLayout android:id"1"
            android:layout_width"match_parent"
            android:background="?android:attr/detailsElementBackground" />

</LinearLayout>

通过布局文件我们知道,系统在activity载入布局的时候实例化TitlesFragment(话剧的标题),FragmentLLayout(显示话剧内容简介的Fragment)占据右边的屏幕但是现在没有内容.就像你下面看到的那样,直到用户选择了标题一个Fragment才会被放到FrameLayout.

然而,不是多有的屏幕配置都足够显示这两个Fragment视图.按照res/layout-land/fragment_layout.xml文件,上面的布局只适合横屏.

那么当屏幕在竖屏的时候,系统会使用下面的布局,保存在res/layout/fragment_layout.xml中.

1
2
3
4
5
6
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width"match_parent">
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width"match_parent" />
</FrameLayout>

这个布局值包含TitlesFragment.这意味着当设备在竖屏的时候,只有话剧的标题是可见的.所以,当用户点击列表的一项的时候,应用将会开始一个新的activity来显示简介而不是载入第二个Fragment.

接下来,你将看到这在Fragment类中是怎么实现的.首先是TitleFragment,显示了莎士比亚话剧的标题.这个Fragment继承自ListFragment,可以通过它实现大多数显示列表信息操作.

正如你看到的那样,注意在用户点击列表响的时候,有两个可能的行为:如果这两个视图存在,将在这个activity中创建并显示一个新的Fragment(把Fragment添加到FragmentLayout中);如果只有一个视图(竖屏),那会启动一个新的activity(Fragment在这个activity中显示).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Populate list with our static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            // Restore last state for checked position.
            mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }

        if (mDualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // Make sure our UI is in the correct state.
            showDetails(mCurCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }

    /* * 
     *  Helper function to show the details of a selected item, either by
     *  displaying a fragment in-place in the current UI, or starting a
     *  whole new activity in which it is displayed.
     * /
    void showDetails(int index) {
        mCurCheckPosition = index;

        if (mDualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            getListView().setItemChecked(index, true);

            // Check what fragment is currently shown, replace if needed.
            DetailsFragment details = (DetailsFragment)
                    getFragmentManager().findFragmentById(R.id.details);
            if (details index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index);

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.replace(R.id.details, details);
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}

第二个Fragment,DetailsFragment显示了在TitleFragment中选中的话剧简介.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static class DetailsFragment extends Fragment {
    /* * 
     *  Create a new instance of DetailsFragment, initialized to
     *  show the text at 'index'.
     * /
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist.  The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed.  Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}

来自TitleFragment的调用,如果用户点击列表项的时候当前布局不包含R.id.details视图(DetailsFragment 所在的视图),那应用将会启动DetailsActivity 来显示选中项的内容简介.

这里是DetailsActivity,在屏幕是竖屏的时候,简单的嵌入在了Fragment中来显示选中的话剧简介.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}

注意activity会在横屏的时候结束自己,这样主activity可以接管并显示DetailsFragment旁边的TitlesFragment.如果用户在竖屏的时候启动DetailsActivity,然后把设备转到横屏(将会重启当前的activity).

更多使用Fragment的例子(包括这个例子的全部代码),请参考API Demo(可以在SDK例子那里下载).

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值