问题记录
背景
使用TabLayout+ViewPager+FragmentPagerAdapter实现经典的多Tab底部展示的布局结构。
需求
动态增删换Fragment
问题症状
删除异常,增加失败,替换失败
问题原因
问题主要还是在于FragmentPagerAdapter的缓存机制:
FragmentPagerAdapter
会缓存所有的经过instantiate()
之后的Fragment
,保存在一个ArrayList<Fragment>
的数据结构中。在从缓存中取出来复用的时候,是根据Container的ID+ItemId共同判断的。这个containerId
是哪个Container
呢?就是ViewPager
。所以,决定缓存取出策略的,基本就是itemId
了。
由此也可以复现问题场景:
假设有4个
Fragment
,现在要将下表为1的那个Fragment
删除。你直接从你的List<Fragment>
中删除了它,然后当你点击第1个时,显示的却还是原来那个。
类似的,增加/替换,也是同样的症状。
解决办法
很简单,就是给每个
Fragment
都固定一个ID,在FragmentPagerAdapter
的getItemId()
方法中根据各种不同的展现情景返回,而不是默认的只返回position
。
在这里,最简单的方式就是返回你的List<Fragment>
中Fragment
的hashCode
。
这样就解决了增删替换的问题,如果要调换位置,也是可以的。
相关系统源码
READ THE FUCKING SOURCE CODE
FragmentPagerAdapter
:
@Override
public Object instantiateItem(ViewGroup container, int position) {
......
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) {
......
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
......
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
......
return fragment;
}
public long getItemId(int position) {
return position;
}
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
FragmentManager
:
public abstract Fragment findFragmentByTag(String tag);
@Override
public Fragment findFragmentByTag(String tag) {
if (mAdded != null && tag != null) {
// First look through added fragments.
for (int i=mAdded.size()-1; i>=0; i--) {
Fragment f = mAdded.get(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
......
return null;
}
其中mAdded
声明是这样的:
ArrayList<Fragment> mAdded;
好了,看了以上的源码足够解决问题了。
网友评论