分类:C#、Android、VS2015;
创建日期:2016-02-22
一、简介
想要管理activity中的fragment,可以用FragmentManager类来实现。通过在activity中调用GetFragmentManager()可获得该类的实例。
使用FragmentManager可以做的事情有:
l 使用FindFragmentById()(用于在activity布局中提供有界面的fragment)或者FindFragmentByTag()获取activity中存在的fragment(用于有界面或者没有界面的fragment)。
l 使用PopBackStack()(模仿用户的BACK命令)从后台栈弹出fragment。
l 使用AddOnBackStackChangedListener()注册一个监听后台栈变化的监听器。
关于这些函数和其它的更多信息,可参考FragmentManager类的文档。
二、创建Fragment
1、添加Example_Fragment.axml文件
通常,fragment构建了其宿主activity的部分界面,它被作为activity全部视图层次体系的一部分被嵌入进去。
下面是含有两个fragment的布局文件:
<?xml version="1.0" encoding="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>
<fragment>中的android:name属性指定了布局中实例化的Fragment类。
注意:Java不允许使用大写字母的包名(Java的“包”在C#中称为“命名空间”),但是在C#中没有这个限制。例如,下面两种方式都是合法的:
<fragment android:name="com.example.DetailsFragment"
android:id="@+id/fragment_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
也可以这样写:
<fragment android:name="Com.Example.DetailsFragment"
android:id="@+id/fragment_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
当系统创建activity布局时,它实例化了布局文件中指定的每一个fragment,并为它们调用OnCreateView()函数,以获取每一个fragment的布局。系统直接在<fragment>元素的位置插入fragment返回的View。
注意:每个fragment都需要一个唯一的标识(ID),其用途是:如果重启activity,系统可用来恢复fragment(并且可用来捕捉fragment的事务处理,例如移除)。
为fragment提供ID有三种方法:
- 用android:id属性提供一个唯一的标识。
- 用android:tag属性提供一个唯一的字符串。
- 如果上述两个属性都没有,系统会使用其容器视图(view)的ID。
也可以通过C#代码将fragment添加到已存在的ViewGroup中。
在activity运行的任何时候,可以将fragment添加到activity布局中,此时仅需要简单指定用来放置fragment的ViewGroup。
2、在.cs文件中创建Fragment
要在.cs文件中创建一个自定义的fragment类,必须让其继承自fragment类或其子类。在自定义的fragment类中实现的代码看起来很像activity,例如重写OnCreate()、OnStart()、OnPause()、OnStop()、……等。事实上,如果你将一个现成的Android应用程序改为用Fragment来实现,甚至可以直接将代码从activity的回调方法中移植到fragment的回调方法中。
下面这段代码通过Inflate()方法Resource/Layout文件夹下的Example_Fragment.axml填充到视图中,并将它作为一个子视图添加到分组容器内。
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.Inflate(Resource.Layout.Example_Fragment, container, false);
}
注意:继承自fragment的子视图必须包含一个默认的无参数的构造函数。
3、需要重写哪些方法
在fragment生命周期内,一般情况下,你至少需要重写(override)以下三个回调方法:Infalate()方法、OnCreate()方法、OnPause()方法。
大部分应用程序都应该至少为每个fragment实现这三个方法,但是还有许多其他用以操纵fragment生命周期中各个阶段的回调函数。所有生命周期中的回调函数在操纵fragment生命周期一节中稍后再做讨论。
除了基类fragment,这里还有几个你可能会继承的子类:
DialogFragment:显示一个浮动的对话框。使用这个类创建对话框是使用Activity类对话框帮助方法之外的另一个不错的选择,因为你可以把fragment对话框并入到由activity管理的fragments后台栈中,允许用户返回到一个已经摒弃的fragment。
ListFragment:显示一个由适配器管理的条目列表(例如SimpleCursorAdapter),类似于ListActivity。它提供了许多管理列表视图的方法,例如OnListItemClick()。
PreferenceFragment:显示一个Preference对象的体系结构列表,类似于preferenceActivity。这在为应用程序创建“设置”activity时是很实用的。
4、将fragment添加到activity布局中
fragment常被用作activity用户界面的一部分,并且将本身的布局构建到activity中去。
为了给fragment提供一个布局,你必须实现OnCreateView()方法,在绘制fragment布局时Android系统会调用它。实现这个方法时需要返回fragment所属的根View。
注意:如果fragment是ListFragment的子类,它会默认实现从OnCreateView()返回一个ListView,所以不需要再实现它。
通常,fragment构建了其宿主activity的部分界面,它被作为activity全部视图层次体系的一部分被嵌入进去。在acitivity布局中添加fragment有两种方式:
(1)方式1--在布局文件中添加fragment
可以像为view指定布局属性那样为fragment指定布局属性。例如:
<?xml version="1.0" encoding="utf-8"?>
<fragment android:name="FragmentDemo.TitlesFragment"
android:id="@+id/titles_fragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<fragment>中的android:name属性指定了布局中实例化的Fragment类。
当系统创建activity布局时,它实例化了布局文件中指定的每一个fragment,并为它们调用OnCreateView()函数,以获取每一个fragment的布局。系统直接在<fragment>元素的位置插入fragment返回的View。
注意:每个fragment都需要一个唯一的标识(ID),其用途是:如果重启activity,系统可用来恢复fragment(并且可用来捕捉fragment的事务处理,例如移除)。
为fragment提供ID有三种方法:
- 用android:id属性提供一个唯一的标识。
- 用android:tag属性提供一个唯一的字符串。
- 如果上述两个属性都没有,系统会使用其容器视图(view)的ID。
也可以通过C#代码将fragment添加到已存在的ViewGroup中。
在activity运行的任何时候,可以将fragment添加到activity布局中,此时仅需要简单指定用来放置fragment的ViewGroup。
(2)方式2--在.cs文件中添加fragment
通过代码添加fragment时,应该使用FragmentTransaction的API来对activity中的fragment进行操作(例如添加、移除,或者替换fragment)。可以像下面这样从Activity中取得FragmentTransaction的实例:
FragmentManager fragmentManager = new FragmentManager()
FragmentTransaction fragmentTransaction = this.FragmentManager.BeginTransaction();
可以用Add()函数添加fragment,并指定要添加的fragment以及要将其插入到哪个视图(view)之中:
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.Add(Resource.id.fragment_container, fragment);
fragmentTransaction.Commit();
传入Add()函数的第一个参数是fragment被放置的ViewGroup,它由资源ID(resource ID)指定,第二个参数就是要添加的fragment。
上面的代码也可以这样写:
ExampleFragment fragment = new ExampleFragment()
.Add(Resource.id.fragment_container, fragment);
.Commit();
一旦通过FragmentTransaction 做了更改,都应当调用Commit()使变化生效。
另外,也可以使用fragment为activity提供后台动作,却不呈现多余的用户界面。
添加没有界面的fragment时,可通过调用Add(Fragment, String)方法来实现,该方法为fragment提供一个唯一的字符串“tag”,而不是视图(view)ID。
用这种办法添加fragment后,因为没有将其关联到activity布局中的视图(view),因此活动不会调用OnCreateView()方法,所以程序中不需要重写OnCreateView()。
为无界面fragment提供字符串标签并不是专门针对无界面fragment的——也可以为有界面fragment提供字符串标签——但是对于无界面fragment,字符串标签是唯一识别它的方法。如果之后想从activity中取到fragment,需要使用FindFragmentByTag()。
二、处理Fragment事务
在activity中使用fragment的一大特点是具有添加、删除、替换,和执行其它动作的能力,以响应用户的互动。提交给activity的一系列变化被称为事务,并且可以用FragmentTransaction 中的APIs处理。
你也可以将每一个事务保存在由activity管理的后台栈中,并且允许用户导航回退fragment变更(类似于activity的回退导航)。
可以从FragmentManager中获取FragmentTransaction实例,像这样:
FragmentManager fragmentManager = new FragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.BeginTransaction();
由于每项事务都是在同一时间内要执行的一系列的变更,因此你可以为一个给定的事务用相关方法设置想要执行的所有变化,例如add()、remove()、replace()。然后,用commit()将事务提交给activity。
但是,在调用Commit()之前,为了将事务添加到fragment的事务后台栈中,你可能希望调用AddToBackStatck()。这个后台栈由activity管理,并且允许用户通过按【Back】键回退到前一个fragment状态。
下面的代码演示如何使用另一个fragment代替一个fragment,并且将之前的状态保留在后台栈中:
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = this.FragmentManager.beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(Resources.id.fragment_container, newFragment);
transaction.AddToBackStack(null);
// Commit the transaction
transaction.Commit();
在这个例子中,newFragment替换了当前在布局容器中用Resources.id.fragment_container标识的所有的fragment(如果有的话),替代的事务被保存在后台栈中,因此用户可以回退该事务,可通过按【Back】键还原之前的fragment。
如果添加多个变更事务,例如另一个Add()或者Remove(),并调用AddToBackStack(),那么在调用Commit()之前的所有应用的变更被作为一个单独的事务添加到后台栈中,并且【Back】键可以将它们一起回退。
将变更添加到FragmentTransaction中的顺序注意以下两点:
- 必须要在最后调用Commit()。如果你正将多个fragment添加到同一个容器中,那么添加顺序决定了它们在视图层次(view hierarchy)里显示的顺序。
- 在执行删除fragment事务时,如果没有调用AddToBackStack(),那么事务一提交fragment就会被销毁,而且用户也无法回退它。然而,当移除一个fragment时,如果调用了AddToBackStack(),那么之后fragment会被停止,如果用户回退,它将被恢复过来。
提示:对于每一个fragment事务,在提交之前通过调用SetTransition()来应用一系列事务动作。
调用Commit()并不立刻执行事务,相反,而是采取预约方式,一旦activity的界面线程(主线程)准备好便可运行起来。然而,如果有必要的话,你可以从界面线程调用ExecutePendingTransations()立即执行由Commit()提交的事务。但这样做,通常是没有必要的,除非其它线程的工作依赖与该项事务。
警告:只能在activity保存状态之前用Commit()提交事务。如果你尝试在那时之后提交,会抛出一个异常。这是因为如果activity需要被恢复,提交后的状态会被丢失。对于这类丢失提交的情况,可通过调用CommitAllowingStateLoss()来解决。
1、与Activity的交互
尽管Fragment被实现为一个对象,它独立于Activity并可以在多个Activity中使用,一个给定的fragment实例直接被捆绑在包含它的Activity中。
特别是,fragment可以通过GetActivity()函数访问Activity,并且很容易的执行类似于查找activity布局中的视图的任务:
View listView = Activity.FindViewById(Resources.id.list);
同样的,activity能够调用fragment的函数FindFragmentById()或者FindFragmentByTag(),从FragmentManager中获取Fragment的索引,例如:
var fragment = (ExampleFragment) FragmentManager.FindFragmentById(Resources.id.example_fragment);
2、创建activity的事件回调函数
在一些情况下,你可能需要fragment与activity共享事件。这样做的一个好方法是在fragment内部定义一个回调接口,并需要宿主activity实现它。当activity通过接口接收到回调时,可以在必要时与布局中的其它fragment共享信息。
举个例子,如果新闻应用的actvity中有两个fragment——一个显示文章列表(fragment A),另一个显示一篇文章(fragment B)——然后fragment A 必须要告诉activity列表项何时被选中,这样,activity可以通知fragment B显示这篇文章。这种情况下,在fragment A内部声明接口OnArticleSelectedListener:
public static class FragmentA : ListFragment
{
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener
{
public void OnArticleSelected(Uri articleUri);
}
...
}
然后fragment的宿主activity实现OnArticleSelectedListener接口,并且重写OnArticleSelected()以通知fragment B来自于fragment A的事件。为了确保宿主activity实现了这个接口,fragment A的OnAttach()回调函数(当添加fragment到activity中时系统会调用它)通过作为参数传入onAttach()的activity的类型转换来实例化一个OnArticleSelectedListener实例。
public static class FragmentA : ListFragment
{
OnArticleSelectedListener mListener;
...
public override void OnAttach(Activity activity)
{
base.OnAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
...
}
如果activity没有实现这个接口,那么fragment会抛出一个ClassCaseException异常。一旦成功,mListener成员会保留一个activity的OnArticleSelectedListener实现的引用,由此fragment A可以通过调用由OnArticleSelectedListener接口定义的方法与activity共享事件。例如,如果fragment A是ListFragment的子类,每次用户点击列表项时,系统都会调用fragment的OnListItemClick()事件,然后fragment调用OnArticleSelected()来与activity共享事件。
public static class FragmentA : ListFragment
{
OnArticleSelectedListener mListener;
...
public override 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是点击的列表项行id,activity(或者其它fragment)用以从应用的ContentProvider获取文章。
关于使用content provider的更多资料可以在在Content Providers文档获取。
3、添加items到菜单项中
你的fragments可以通过实现OnCreateOptionsMenu()来构建菜单项到activity的Options Menu(Action Bar也一样)。为了使用这个方法接收到调用,不管怎样,你必须在OnCreate()期间调用SetHasOptionsMenu(),来指明想要添加项目到Options Menu的那个fragment(否则,fragment将接收不到onCreateOptionsMenu()的调用)。
任何想要在fragment中的Options Menu添加的项目都追加到已有的菜单项后面。当菜单项被选中时,fragment也会接收到对OnOptionsItemSelected()的回调。
你也可以通过调用registerForContextMenu()在fragment布局中注册一个view以提供一个右键快捷菜单(context menu)。当用户打开context menu时,fragment接收到对OnCreateContextMenu()的回调。当用户选中一个项目时,fragment接收到对OnContextItemSelected()的回调。
注意:尽管你的fragment会接收到为添加到每个菜单项被选择菜单项的回调,但当用户选择一个菜单项时,activity会首先接收到对应的回调。如果activity的选择菜单项回调的实现没有处理被选中的项目,那么该事件被传递给fragment的回调。这同样适用于Options Menu和context menu。
关于菜单的更多信息,请参考菜单和Action Bar开发指南。
三、处理Fragment的生命周期
管理fragment生命周期与管理activity生命周期很相像。像activity一样,fragment也有三种状态:
- Resumed:fragment在运行中的activity可见。
- Paused:另一个activity处于前台且得到焦点,但是这个fragment所在的activity仍然可见(前台activity部分透明,或者没有覆盖全屏)。
- Stopped:fragment不可见。要么宿主activity已经停止,要么fragment已经从activity上移除,但已被添加到后台栈中。一个停止的fragment仍然活着(所有状态和成员信息仍然由系统保留着)。但是,它对用户来讲已经不再可见,并且如果activity被杀掉,它也将被杀掉。
同activity类似的还有,你也可以用Bundle保存fragment状态,万一activity的进程被杀掉了,并且在activity被重新创建时,你需要恢复fragment状态。在回调执行fragment的OnSaveInstanceState()期间可以保存状态,在OnCreate(),OnCreateView(),或者OnActvityCreate()中可以恢复状态。更多关于保存状态的信息,参考Activities文档。
1、管理fragment的生命周期
在生命周期方面,activity与fragment之间一个很重要的不同,就是在各自的后台栈中是如何存储的。当activity停止时,默认情况下,activity被安置在由系统管理的activity后台栈中(因此用户可以按【Back】键回退导航,就像在Tasks和后台栈中讨论的那样)。但是,仅当你在一个事务被移除时,通过显式调用AddToBackStack()请求保存的实例,该fragment才被置于由宿主activity管理的后台栈。
除此之外,管理fragment的生命周期与管理activity的生命周期非常相似。所以,管理activity生命周期的实践同样也适用于fragment。你需要了解的,仅仅是activity的生命周期如何影响fragment的的。
2、与activity生命周期的协调合作
fragment所生存的activity生命周期直接影响着fragment的生命周期,由此针对activity的每一个生命周期回调都会引发一个fragment类似的回调。例如,当activity接收到OnPause()时,这个activity之中的每个fragment都会接收到onPause()。
Fragment有一些额外的生命周期回调方法,然而,为了处理像是创建和销毁fragment界面,它与activity进行独特的交互。这些额外的回调方法是:
OnAttach():当fragment被绑定到activity时调用(Activity会被传入)。
OnCreateView():创建与fragment相关的视图体系时被调用。
OnActivityCreated():当activity的OnCreate()函数返回时被调用。
OnDestroyView():当与fragment关联的视图体系正被移除时被调用。
OnDetach():当fragment正与activity解除关联时被调用。
这些方法的详细介绍见11.4节。
fragment的生命周期流程实际上是受其宿主activity影响。例如,当activity接收到它的OnCreate()回调时,activity之中的fragment接收到的仅仅是OnActivityCreated()回调。
一旦activity处于Resumed状态,则可以在activity中自由的添加或者移除fragment。因此,只有当activity处于resumed状态时,fragment的生命周期才可以独立变化。
然而,当activity离开恢复状态时,fragment再一次被activity推入它的生命周期中。
四、几个特殊的Fragment类
Fragments API提供了以下子类,利用这些类可实现一些通用的功能。这些类有:
- ListFragment – 将Fragment作为列表项来使用。
- DialogFragment – 将Fragment作为对话框来显示。
- PreferenceFragment – 将Fragment作为一系列偏好项来显示。
下一节我们将用具体例子演示Fragment的基本用法。