7.5.2 界面片段
界面片段(Fragment),体现了Android界面元素的新组织方式。在老版本的Android中,界面组件承载了管理对象生命周期和组织交互界面两项工作,引入界面片段,可以将大部分的界面组织和控制工作交由界面片段处理,界面组件只需将需要的界面片段进行拼接和控制即可。
界面片段的设计,沿袭了界面组件的模式,如图7-19所示,其中的关键函数与界面组件生命周期中的函数一一对应。在开发时,开发者只需要把原有界面组件的交互界面的定制逻辑,按照此关系进行移植即可。
最常见定义界面片段的定制方式如下:
public static class MyFragment extends Fragment{
//在界面初始化时调用,用来构建界面片段中提供的交互界面
//这是在实现界面片段时,必须要重载的函数
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState){
//通过样式my_fragment构造界面控件对象
View view=inflater.inflate(R.layout.my_fragment, container, false);
return view;
}
}
其中,Fragment.onCreateView函数是必须要重载的,用来构造界面片段对应的控件对象,其他的函数,则会根据生命周期的需求选择实现。比如,在Android原生的邮箱应用中,邮件列表是通过界面片段来构建的,其中会重载多个生命周期相关的函数,用来进行事件监听和数据处理:
图 7-19 界面片段Fragment与界面组件Activity生命周期的对应关系[1]
//邮件列表片段派生自android.app.ListFragement
//这说明它是一个基于列表的界面片段,与ListActivity类似
public class MessageListFragment extends ListFragment
implements OnItemLongClickListener, MessagesAdapter.Callback,
MoveMessageToDialog.Callback, OnDragListener, OnTouchListener{
//界面片段对象被构造时,会调用该函数,通常用于初始化一些成员变量
@Override
public void onCreate(Bundle savedInstanceState){
//在邮件列表中,会缓存其宿主界面组件对象以及列表的适配器对象等
super.onCreate(savedInstanceState);
mActivity=getActivity();
mListAdapter=new MessagesAdapter(mActivity, this);
mRefreshManager=RefreshManager.getInstance(mActivity);
…
}
//在界面组件Activity.onCreate函数执行完成后,调用该函数
//这是一个绝好的初始化交互界面状态的时机
@Override
public void onActivityCreated(Bundle savedInstanceState){
//在邮件列表中,会对列表进行一些初始化设置
super.onActivityCreated(savedInstanceState);
final ListView lv=getListView();
lv.setOnItemLongClickListener(this);
lv.setOnTouchListener(this);
lv.setItemsCanFocus(false);
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
…
//如果是一次恢复性构造,savedInstanceState不为空
//可以在这里对界面状态进行恢复
if(savedInstanceState!=null){
restoreInstanceState(savedInstanceState);
}
…
}
//在界面组件切换到前台时,会调用界面片段中的该函数
//常在此函数中绑定一些事件回传函数,用以接收消息刷新界面
@Override
public void onResume(){
super.onResume();
//当邮件列表切换到前台后,停止新邮件的通知,并监听新邮件事件
//使得有列表在前台且新邮件来到时,不发出通知,只刷新邮件列表
adjustMessageNotification(false);
mRefreshManager.registerListener(mRefreshListener);
…
}
//在界面组件从前台切换到后台时,会调用界面片段中的该函数
//常在此函数中注销对一些事件的监听,使得界面在后台时不处理这些事件
@Override
public void onPause(){
…
//当邮件列表切换到后台后,打开新邮件通知
adjustMessageNotification(true);
super.onPause();
}
//在界面组件完全不可见后,会调用界面片段中的该函数
//常在此函数中注销对一些事件的监听,使界面不可见后不处理这些事件
@Override
public void onStop(){
…
//在邮件列表不可见后,不再监听新邮件事件,不刷新邮件列表
mRefreshManager.unregisterListener(mRefreshListener);
super.onStop();
}
}
通过对原生邮件列表界面片段的介绍可以看出,界面片段的使用和控制与界面组件相似,在实际开发中,开发者需要充分利用生命周期相关的函数,妥善处理好交互细节。
有了界面片段,整个交互界面的构建方式变得灵活起来。在界面组件的实现中,常通过两种模式来基于界面片段构建交互界面,最基本的一种称为静态拼接,即通过资源文件的描述,通过一个或多个界面片段拼接出界面组件所需的交互界面来。
比如,在很多应用的交互界面设计中,都需要在左边显示列表,在右边呈现所选列表项的详情内容。在界面组件中,开发者可以将负责列表呈现的界面片段和负责详情呈现的界面片段整合在一起,利用资源文件将交互界面定义出来:
定义界面样式资源文件main.xml
<?xml version="1.0"encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<!—左侧是列表界面片段—>
<fragment android:id="@+id/list_fragment"
class="com.duguhome.MyListFragment"
android:layout_height="match_parent"
android:layout_width="150dp"/>
<!—右侧是详情界面片段—>
<fragment android:id="@+id/detail_fragment"
class="com.duguhome.MyDetailFragment"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</LinearLayout>
将样式设置到界面组件中
@Override
protected void onCreate(Bundle savedState){
super.onCreate(savedState);
setContentView(R.layout.main);
…
}
其中,<fragment>元素就是用来在资源文件中定义界面片段的,其class属性是用来指明所定义界面片段的实现类。例如,左侧列表的实现类是com.duguhome.MyListFragment,就需要通过class属性进行声明,并在代码中将其实现:
public class MyListFragment extends ListFragment{
@Override
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
//重载该方法,并在其中设置对应的adapter
setListAdapter(CreateMyListAdapter());
}
@Override
public void onListItemClick(ListView l, View v, int position, long id){
//重载列表点击事件,来处理点击列表项事件
…
}
}
通过界面片段静态地拼接交互界面,常用在界面格局比较固定的情况下。而除此之外,界面片段不仅可以静态地拼接在一起,还可以进行动态拼接,针对当前屏幕尺寸、不同场景下的界面交互需求,动态挑选所需的界面片段,在界面组件中构造出交互界面。
通过Activity.getFragmentManager函数,可以获取该界面组件的android.app.FragmentManager对象。FragmentManager对象用来管理界面组件中需要动态调度的界面片段。开发时,可以将多个需要使用的界面片段对象添加到FragmentManager中,便于后续的调度和切换。当需要在多个不同的动态界面片段间进行切换时,可以使用android.app.FragmentTransaction对象来控制,顾名思义,FragmentTransaction对象是将针对界面片段的多个动作封装成事务,一次性地进行执行,来提高执行效率,更为平滑地实现切换效果。
举个例子,如果在界面中需要实现类似于选项卡的效果,在不同的功能区域进行切换时,就可以使用多个界面片段呈现不同功能区域的交互界面,利用FragmentManager进行管理,使用FragmentTransaction进行切换:
//开始切换界面片段的事务
FragmentManager fragmentManager=getFragmentManager();
FragmentTransaction transaction=
fragmentManager.beginTransaction();
//按照预设的标志来查找界面片段对象
//因为所有的界面片段都添加到了同一个容器内,所以不能通过id查找
Fragment pageA=fragmentManager.findFragmentByTag("A");
Fragment pageB=fragmentManager.findFragmentByTag("B");
Fragment pageC=fragmentManager.findFragmentByTag("C");
//如果还未能找到界面片段,则添加
//所有的界面片段会被放到同一个容器R.id.main中
if(pageA==null){
pageA=new MyPageFragmentA();
pageB=new MyPageFragmentB();
pageC=new MyPageFragmentC();
transaction.add(R.id.main, pageA,"A");
transaction.add(R.id.main, pageB,"B");
transaction.add(R.id.main, pageC,"C");
}
//设置需要显示的界面片段,在这里,假设pageA需要优先显示
transaction.show(pageA);
transaction.hide(pageB);
transaction.hide(pageC);
//直到这个时候,所有对界面片段的操作还未生效
//等到所有需要做的修改都完成了,再将事务提交,而后,所有修改生效
transaction.commit();
使用界面片段来构建交互界面,可用的手段非常丰富,除了通过FragmentTransaction.show、hide控制不同界面片段的显隐性,还可以使用FragmentTransaction.replace、add、remove等函数,对需要的界面片段进行动态的替换。
此外,FragmentTransaction还提供了addToBackStack函数,使得多个界面片段对象可以构成一个界面片段栈。比之传统的界面组件栈,界面片段栈更为轻量,使用更为灵活:
//开始切换界面片段的事务
FragmentManager fragmentManager=getFragmentManager();
FragmentTransaction transaction=
fragmentManager.beginTransaction();
//构造新的界面片段,取代老的,并将老的压栈,点击回退键时,再次构建
Fragment next=new MyDetailFragment();
transaction.replace(R.id.main, next);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
//提交操作
transaction.commit();
总而言之,使用界面片段,可以将界面构造逻辑从界面组件中抽离出来,针对不同设备不同场景更方便地构造交互界面[2]。
[1]修改自SDK文档中介绍界面片段生命周期部分,原图参见:https://developer.android.com/guide/topics/fundamentals/fragments.html#lifecycle。
[2]关于Fragments的相关介绍,可以参见SDK文档:http://developer.android.com/guide/topics/fundamentals/fragments.html。