起因是因為TabActivity被Google死亡宣告(deprecated)。
加上客戶的無理要求「在所有的頁面都要有側滑頁」。
首先使用FragmentTabHost,因為跟之前的TabHost的程式碼接近,稍作修改就可以實做出5個Fragment。
但是tab1->tab1-2->tab2->back鍵->回到tab1,發現還是停留在tab1-2並不會回去tab1。很明顯的是FragmentManager的backstack沒辦法處理。
只好硬著頭皮再找其他方案。簡單搜尋了一下,發現android OS 4.2之後出現getChildFragmentManager(),
似乎是可以做到讓fragment裡面還可以有自已的History。於是朝這個方向嘗試。
找到了一篇介紹這類的方法的網頁,裡面使用tabhost跟ontabchanged listener去處理。 本篇就是建構此文之上去做的改善。
設計概念:
- 使用一個FragmenActivity。加上FragmentTabHost讓此Activity可以使用tab 來做 fragment的切換。
在FragmenActivity上面建立N個tab在android.R.id.tabcontent上面操作的fragment。 - 這N個fragment都使用一個設計好的空fragment class建立。
並在此空的N個fragment上面,利用android os 4.2版之後新增的getChildFragmentManager()函式,可以分別取得此N個空fragment的FragmentManager,fragment Transaction history功能即可依tab來分別。
(空fragment class的特性:layout xml只有一個給ChildFragmentManager利用的framelayout,另外加上使用ChildFragmentManager來做回上一層fragment的功能給FragmenActivity取用。) - 各個空fragment直接進行第一次的fragment Transaction。如此就不會出現空白畫面。
要繼續往下進行fragment Transaction的話,使用getSupportFragmentManager()就可以拿到該層的FragmentManager。(也就是空fragment class的FragmentManager。)
注意下面幾點:
- 此文裡面浪費了一個framelayout給Tabhost使用,本方法改善此設計。
- 截自android support library 20版為止,android.support.v13.app的fragment不支援getChildFragmentManager()。
- 有在fragment的onCreateView()保存之前已經inflate好的fragment layout的習慣的話,記得一定要在第二次執行oncreateview()之後對parentview去做clearallview()的動作。(因為原本的layout已經被attach到fragment container之後,執行fragment的re-attach/replace等等動作的時候會因為原本的view已經被attach,有parent了,導致Exception發生。)或是改成每次onCreateView()都重新inflate layout。
這個問題卡了我近一天... 因為Exception完全沒有提示正確的問題點... - getChildFragmentManager(),操作的fragment container為此fragment inflate的layout裡面存在的某一個layout。(不是getSupportFragmentManager()的那個)
- 假如你還想在這樣的架構之下加用ViewPager + FragmentPagerAdapter的話,請使用「FragmentStatePagerAdapter」。它會幫你處理切換tab時所需要處理的fragment load/unload 問題。
系統架構:
public class MainActivity extends FragmentActivity implements View.OnClickListener, TabHost.OnTabChangeListener { public static final String ROOT_FRAGMENT_ARGS = "tabrootFragment"; private boolean bBackPressed = false; private FragmentTabHost tabHost; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.mainlayout); tbWidget = (TabWidget)findViewById(android.R.id.tabs); tabHost = (FragmentTabHost)findViewById(android.R.id.tabhost); tabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent); tabHost.setOnTabChangedListener(this); addTab("tab1", "Tab1", Fragment1.class); addTab("tab2", "Tab2", Fragment1.class); addTab("tab3", "Tab3", Fragment1.class); } private void addTab(String tabId, String indicactor, Class fragmentClass, Bundle args) { if (args == null) args = new Bundle(); args.putString(ROOT_FRAGMENT_ARGS, fragmentClass.getName()); tabHost.addTab(tabHost.newTabSpec(tabId).setIndicator(indicactor), EmptyFragment.class, args); } } public EmptyFragment getCurrentFragment() { return (EmptyFragment) getSupportFragmentManager().findFragmentById(android.R.id.tabcontent); } @Override public void onBackPressed() { EmptyFragment rootFragment = getCurrentFragment(); if (rootFragment == null || !rootFragment.popBackStack()) { if (bBackPressed) { finish(); } else { bBackPressed = true; mHandler.postDelayed(new Runnable(){ public void run() { bBackPressed = false; } }, 3000); Utils.showToast(this, "Press Back twice to exit app"); } } }
EmptyFragment:
public class EmptyFragment extends Fragment { private View contentView = null; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); if (contentView == null) contentView = inflater.inflate(R.layout.emptyfragment, container, false); else ((FrameLayout)contentView.getParent()).removeAllViews(); //prevent fragment history return child attached exception. return contentView; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); initFragment(); } /** * Pop fragment backstack. * @return return true if exists backstack */ public boolean popBackStack() { return getChildFragmentManager().popBackStackImmediate(); } /** * Initialize Fragment. */ private void initFragment() { Bundle args = getArguments(); String rootClass = args.getString(MainActivity.ROOT_FRAGMENT_ARGS); FragmentManager fragmentManager = getChildFragmentManager(); if (fragmentManager.findFragmentById(R.id.fragment) != null) { return; } FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); Fragment fragment = Fragment.instantiate(getActivity(), rootClass, args); fragmentTransaction.replace(R.id.fragment, fragment); fragmentTransaction.commit(); } /** * Return Fragment in displayed * @return Fragment */ Fragment getCurrentFragment() { return getChildFragmentManager().findFragmentById(R.id.fragment); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // Call child fragment method getCurrentFragment().onActivityResult(requestCode, resultCode, data); } }
R.layout.mainlayout:
R.layout.emptyfragment: