8.自定義BACK按鍵
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()方法的返回值)的版本。使用這個方法,無須包括標記也可以直接跳到根檢視。