上一个项目遇到了一个Activity 管理30个Fragment的情况,刚开始的时候真的管理的焦头烂额,但是后来不停的研究api文档,渐渐的明白了android的Fragment管理体系。下面用一种Fragment嵌套Fragment的情况来总结一下Fragment的管理。
上图的布局,大神立马就能看出来,最外面一个Activity,底下首页,分类,购物车..是第一层一个FragmentTabHost,而首页里面的类别01,02....是用的开源库TabPageIndicator管理的ViewPager,管理这第二层的n个Fragment。
1.当遇到这种嵌套多层的Fragment的时候第一个主要点就是第一层的FragmentManager,可以通过getSupportFragmentManager()或者getFragmentManager()获得,但是在第二层的fragment中如果想获得FragmentManager就不能这样了,必须用getChildFragmentManager()。
2.当点击一个其中一个商品的详情的时候,如果想保持底部的TabHost那就不能用Activity,还得保持Fragment,其实还有另一中方案,那就是最外面的Activity用TabHostActivity,为什么没用尼,因为TabHostActivity已经被废弃了,当我看到那条废弃的横线时,就好像看到一个美女脸上被刀开花了一样,心痛啊,不能娶这样的媳妇啊,直接换FragmentTabHost,黄花大闺女,唉呀妈呀,老装逼了。回到需求,当点击其中一个商品时候,要将底部的TabHost保持,上面的布局都换成详情的布局,看下Api,FragmentTransaction提供add,replace俩个方法,那么用add还是replace尼?使用add()加入fragment时将触发onAttach(),使用attach()不会触发onAttach(),使用replace()替换后会将之前的fragment的view从viewtree中删除。
触发顺序:
detach()->onPause()->onStop()->onDestroyView()
attach()->onCreateView()->onActivityCreated()->onStart()->onResume()
使用hide()方法只是隐藏了fragment的view并没有将view从viewtree中删除,随后可用show()方法将view设置为显示
而使用detach()会将view从viewtree中删除,和remove()不同,此时fragment的状态依然保持着,在使用attach()时会再次调用onCreateView()来重绘视图,注意使用detach()后fragment.isAdded()方法将返回false,在使用attach()还原fragment后isAdded()会依然返回false(需要再次确认)
执行detach()和replace()后要还原视图的话, 可以在相应的fragment中保持相应的view,并在onCreateView()方法中通过view的parent的removeView()方法将view和parent的关联删除后返回。
3.在复杂的Fragment管理中,经常会遇到 Fragment already added 错误,解决这样的错误方法就是,每次添加Fragment,先findFragmentByTag,如果找到了fragment.isAdded(),那么就return,跳出,如果Fragment没在栈中,那就把Fragment Add上去,下面放出一个Fragment管理的公用类
/** * Fragment跳转 * @param fm * @param fragmentClass * @param tag * @param args */ public void turnToFragment(FragmentManager fm, Class<? extends Fragment> fragmentClass, String tag, Bundle args) { mIsCanEixt = false; Fragment fragment = fm.findFragmentByTag(tag); boolean isFragmentExist = true; if (fragment == null) { try { isFragmentExist = false; fragment = fragmentClass.newInstance(); fragment.setArguments(new Bundle()); } catch (java.lang.InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } if(fragment.isAdded()){ return; } if( args != null && !args.isEmpty() ) { fragment.getArguments().putAll(args); } FragmentTransaction ft = fm.beginTransaction(); ft.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out); if( isFragmentExist ) { ft.replace(R.id.realtabcontent, fragment); } else { ft.replace(R.id.realtabcontent, fragment, tag); } ft.addToBackStack(tag); ft.commitAllowingStateLoss(); }最后为什么用
ft.commitAllowingStateLoss();而不是
ft.commit();尼?commitAllowingStateLoss和commit的区别是当退出activity时,防止提交后的状态丢失。对于你觉得可以丢失提交的状况,使用 commitAllowingStateLoss()。
4.最后再介绍一种情况,还是上面的图,当在首页里面,进去了很多层,栈上面叠加了很多Fragment的时候,如果想再次点击TabHost的首页,能返回到最初首页的页面的话,那就要把首页的Fragment上面的Fragment的弹出,TabHost的再次点击事件:
mTabHost.getTabWidget().getChildAt(i) .setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); mTabHost.setCurrentTab(j); mIsCanEixt = true; } });
解释一下:
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);google提供了几种弹出栈的方法:
public abstract void popBackStack ()
Pop the top state off the back stack. This function is asynchronous -- it enqueues the request to pop, but the action will not be performed until the application returns to its event loop.
public abstract void popBackStack (String name, int flags)
public abstract void popBackStack (int id, int flags)
public abstract boolean popBackStackImmediate (int id, int flags)
popBackStack(),配套使用的是ft.addToBackStack(tag);能在按返回键的时候,返回上一个fragment
popBackStack (String name, int flags),如果name不为空,那么在这个Fragment上面的Fragment都会被弹出
flags |
Either 0 or POP_BACK_STACK_INCLUSIVE . |
---|
POP_BACK_STACK_INCLUSIVE
.
5.最最后,再加一个Fragment问题的解决,当Fragment的栈里面有几个fragment的时候,会出现,当你触摸当前fragment的时候,下层的fragment的事件被触发,这是由于Touch事件泄露传到了下层中。解决方法就是拦截onTouch事件
// 将上层的触摸事件拦截 @Override public boolean onTouch(View v, MotionEvent event) { return true; }在onViewCreated里面设置监听
@Override public void onViewCreated(View view, Bundle savedInstanceState) { // 拦截触摸事件,防止泄露下去 view.setOnTouchListener(this); App.showLog(this.getClass().getSimpleName() + " onViewCreated()"); super.onViewCreated(view, savedInstanceState); }