FragmentPagerAdapter中有大约20个片段,它包含一个从中挑选片段的列表,因此不需要重新创建片段。
private List<TitledFragment> fragments;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
this.fragments = new ArrayList<TitledFragment>();
}
@Override
public Fragment getItem(int i) {
return fragments.get(i).getFragment();
}
@Override
public int getCount() {
return fragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return fragments.get(position).getTitle();
}
public synchronized List<TitledFragment> getFragments() {
return fragments;
}
public synchronized void addFragment(TitledFragment fragment) {
fragments.add(fragment);
notifyDataSetChanged();
}
FragmentPagerAdapter被设置为ViewPager的适配器,之后我对片段进行重新排序(洗牌只是为了测试)
Collections.shuffle(mSectionsPagerAdapter.getFragments());
mSectionsPagerAdapter.notifyDataSetChanged();
由于某种原因,只有标题的顺序被更改,即使它为getItem返回不同的片段,因为顺序不同。为什么会这样?我该如何避开它?
您还需要覆盖getItemPosition()
。默认情况下,此函数始终返回POSITION_UNCHANGED
,因此当您调用notifyDataSetChanged()
时,适配器会假设所有片段仍在同一位置。(它不会再次调用getItem()
,因为它认为所有必要的片段都已经创建。)新的getItemPosition()
应该在shuffling后返回每个片段的新位置。
一个简单的破解方法是始终返回POSITION_NONE
,然后适配器将丢弃所有现有的片段,并在调用getItem()
时在正确的位置重新创建它们。当然,这不是有效的,并且具有额外的缺点,即当前正在查看的片段可能会改变。然而,由于在重新排序片段时可能存在一些错误——请参阅我的问题——POSITION_NONE
方法可能是最安全的方法。
接受的答案是非最优答案。从int getItemPosition(Object)
返回POSITION_NONE
只会破坏高效碎片管理的任何希望,需要重新实例化所有碎片。它还忽略了另一个问题。FragmentPageAdapter将Fragment的缓存副本保存在FragmentManager中,并在实例化新Fragment时查找这些副本。如果它发现了它认为匹配的片段,则不调用public Fragment getItem(int)
方法,而是使用缓存的副本。
例如,假设加载了页面0和1,则FragmentManager中会有标记为0和1的缓存片段。现在在索引0处插入一个页面(不要忘记调用notifyDataSetChanged()
),旧索引0变为1,1变为2(这是使用方法public int FragmentPageAdapter.getItemPosition(Object)
发出的信号)。对于项目0,返回了POSITION_NONE(因为它是新位置),因此对位置0:调用方法public Object instantiateItem(ViewGroup, int)
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);
...
看看会发生什么,在位置0找到一个缓存的Fragment,您想要的位置1中的片段现在在位置0,您想要在位置2中的片段在位置1,在位置2中,您得到了FragmentPageAdapter.getItem(int)返回的一个新片段,它是位置1的副本。
如何解决这个问题?我看到了许多关于SO的建议,包括:
- 始终从FragmentPageAdapter.getItemPosition()返回POSITION_NONEhttps://stackoverflow.com/a/7386616/2351246-这个答案忽略了效率和内存管理(如果不清除缓存的碎片,最终将无法工作)
- 追踪碎片标签https://stackoverflow.com/a/12104399/2351246,这取决于可能更改的实现细节
- 最糟糕的是通过反向工程FragmentPageAdapter实现来使用魔术https://stackoverflow.com/a/13925130/2351246,这太可怕了
不需要破坏内存管理或跟踪FragmentPagerAdapter
的内部实现细节。所有答案中缺少的细节是,想要重新排序片段的FragmentPagerAdapter必须同时实现方法public long getItemId(int position)
:
@Override
public long getItemId(int position) {
return System.identityHashCode(fragments.get(position));
}
这提供了一个不基于位置的ID,即使它移动页面,也可以用来在FragmentManager缓存中查找正确的片段。