8.1 问题
应用程序要以自己的方式来处理用户按下物理BACK按键后的行为。
8.2 解决方案
(API Level 5)
可以在Activity中使用onBackPressed()回调方法或者在Fragment中操作回退栈。
8.3 实现机制
如果想要用户在你的Activity上按下BACK按键时可以得到相应通知,可以覆写onBackPressed()方法,如下所示:
@Override
public void onBackPressed() {
//实现自定义返回功能
//调用super以进行常规处理(例如销毁Activity)
super.onBackPressed();
}
这个方法的默认实现会将当前回退栈中的Fragment弹出并且销毁Activity。如果不打算改变这个流程,只需要确保调用父类的实现来保存这种常规的处理方式。
警告:
覆写物理按键事件时应保持慎重。在Android系统中,所有的物理按键都有一致的功能,如果这些按键的功能变化太大,会让用户感到困惑和不满。
###BACK操作和Fragment
当UI中包含Fragment时,可以进一步自定义设备的BACK按键的行为。默认情况下,在UI中添加或替换Fragment的操作并不会在任务的回退栈中添加相应的Fragment,因此当用户按下BACK按键后,并不能够回退这些动作。但是,所有的FragmentTransaction都可以作为条目通过简单地调用addToBackStack()(在事务提交前)添加到回退栈中。
默认情况下,当用户按下BACK按键后,Activity会调用FragmentManager.popBackStackImmediate(),这样每个通过这种方式添加的FragmentTransaction都会在每次点击时弹出,直到栈中一个不剩,然后Activity会被销毁。另外,这个方法还有一些变体,允许直接跳到栈中的某个位置。让我们看一下代码:
res/layout/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="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Go Home"
android:onClick="onHomeClick" />
<FrameLayout
android:id="@+id/container_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
自定义Fragment回退栈的Activity
public class MyActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.container_fragment, MyFragment.newInstance("First Fragment"));
ft.commit();
ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container_fragment, MyFragment.newInstance("Second Fragment"));
ft.addToBackStack("second");
ft.commit();
ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container_fragment, MyFragment.newInstance("Third Fragment"));
ft.addToBackStack("second");
ft.commit();
ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container_fragment, MyFragment.newInstance("Fourth Fragment"));
ft.addToBackStack("fourth");
ft.commit();
}
public void onHomeClick(View v) {
getSupportFragmentManager().popBackStack("second", FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
public static class MyFragment extends Fragment {
private CharSequence mTitle;
public static MyFragment newInstance(String title) {
MyFragment fragment = new MyFragment();
fragment.setTitle(title);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
TextView text = new TextView(getActivity());
text.setText(mTitle);
return text;
}
public void setTitle(CharSequence title) {
mTitle = title;
}
}
}
注意:
这里我们使用了支持库,允许Android3.0之前的版本使用Fragment。如果应用程序的目标API Level为11或更高版本,可以将FragmentActivity用Activity替换,将getSupportFragmentManager()用getFragmentManager()替换。
这个示例会向栈中放入4个自定义的Fragment实例,所以在应用程序运行时会显示最后添加的那个Fragment实例。对于每个事务,我们调用addToBackStack(),同时传入一个标记名称来标记这个事务。如果不想直接跳转到栈中某个位置,那么不需要执行以上操作,直接传入null即可。每次按下BACK按键后,会移除一个Fragment实例,直到只剩下第一个Fragment实例,这时再按下BACK按键,Activity就会被正常销毁。
注意,第一个事务并没有添加到栈中,这是因为我们希望第一个Fragment作为根视图。如果将它也加入到回退栈中,会导致它在Activity销毁前被弹出栈,这就使UI会有空白状态出现。
这个应用程序还要“Go Home”按钮,它可以让用户无论在哪个界面都可以立即回到根Fragment。这是通过调用FragmentManager的popBackStack()方法,同时传入希望跳转的事务的标记名称实现的。我们还传入了POP_BACK_STACK_INCLUSIVE标识来告诉管理器在栈中移除我们标识的事务。如果没有这个标识,这个示例就会跳转到第二个Fragment,而不是根视图。
注意:
Android会跳转到第一个匹配给定标记的事务。如果同一个标记被使用多次,则会跳到第一个添加此标记的事务,而不是最新的事务。
我们不能使用这个方法直接跳到根视图,因为我们无法引用那个事务在回退栈中的标记。这个方法还有一个使用唯一事务的ID(ID为FragmentTransaction的commit()方法的返回值)的版本。使用这个方法,无须包括标记也可以直接跳到根视图。