Android应用ViewPager和TabLayout动态加载Fragment
1 应用背景和目的
项目中需要实现根据服务器端返回的主题模块加载相应的主题内容,其中有三个主题模块为默认加载,该应用程序启动时手机端需加载出来,其余主题模块根据网络请求服务器端返回的主题进行加载。达到由服务器端自动配置主题,推送给手机用户动态加载相应主题内容的效果。
下图为模拟示例,其中tab0、tab1、tab2为默认主题,tab3、tab4、tab5为网络端推动过来的主题模块,并且加载插入的位置在tab0与tab1之间。
2 实现框架
Android端app利用ViewPager和TabLayout的方式实现相应主题内容Fragment的动态加载。示例程序app name为VFTest,运用这个方式,动态加载fragment之后,切换tab,会出现程序崩溃,如下图所示。
查看崩溃得logcat日志如下图所示:
崩溃得原因是Can't change tag of fragment CommonFragment{44d090b0#1 id=0x7f0c0071 android:switcher:2131492977:1}。分析知FragmentPagerAdapter为之前加载好的Fragment设置的tag,由于在原有位置插入了一个新的Fragment,导致两个tag不一致,switcher在切换时出错抛出的异常。
3 解决方案
根据问题,拟解决方案是,当在原有位置加载新的Fragment时,重新调用FragmentPagerAdapter的instantiateItem接口,刷新adapter的中fragment。查看instantiateItem源码,
@Override public Object instantiateItem(ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; }
其中final long itemId = getItemId(position);决定是否重新加载新的Fragment,于是需要在自定的FragmentPagerAdapter中重写getItemId,得到不同的itemId,即:
@Override public long getItemId(int position) { super.getItemId(position); if (fragments != null) { if (position < fragments.size()) { return fragments.get(position).hashCode(); //important } } return super.getItemId(position); }
不同的Fragment分配的HashCode不同,从而实现刷新adapter中的fragment。
当然可能还遇到另一个问题,就是加载的Fragment的内容没有来得及刷新,这里在动态加载之后,还需要记录当前ViewPager的Item的位置,动态加载之后,需要跳到其对应的原有Fragment重置之后的位置,才能即时刷新Fragment内容。