Android在FragmentPagerAdapter中更改碎片顺序



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的建议,包括:

  1. 始终从FragmentPageAdapter.getItemPosition()返回POSITION_NONEhttps://stackoverflow.com/a/7386616/2351246-这个答案忽略了效率和内存管理(如果不清除缓存的碎片,最终将无法工作)
  2. 追踪碎片标签https://stackoverflow.com/a/12104399/2351246,这取决于可能更改的实现细节
  3. 最糟糕的是通过反向工程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缓存中查找正确的片段。

最新更新