2

我在 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 返回不同的片段,因为顺序不同。为什么会这样,我该如何解决?

4

2 回答 2

9

您还需要覆盖getItemPosition(). 默认情况下,此函数始终返回POSITION_UNCHANGED,因此当您调用 时notifyDataSetChanged(),适配器假定所有片段仍位于同一位置。(它不会getItem()再次调用,因为它认为所有必要的片段都已创建。)你的 newgetItemPosition()应该在洗牌后返回每个片段的新位置。

一个简单的技巧是总是返回POSITION_NONE,然后适配器将丢弃所有现有的片段,并在调用时在正确的位置重新创建它们getItem()。当然,这样做效率不高,并且还有一个额外的缺点,即正在查看的当前片段可能会发生变化。但是,由于在重新排序片段时可能存在一些错误 - 请参阅我的问题- 该POSITION_NONE方法可能是最安全的方法。

于 2012-09-24T01:29:56.087 回答
1

接受的答案是非最佳答案。POSITION_NONE从just 中返回int getItemPosition(Object)会破坏任何高效片段管理的希望,需要重新实例化所有片段。它也忽略了另一个问题。FragmentPageAdapter 在 FragmentManager 中保存了 Fragment 的缓存副本,并在实例化新的 Fragment 时查找这些副本。如果它发现它认为是匹配的片段,public Fragment getItem(int)则不调用该方法并使用缓存的副本。

例如,假设页面 0 和 1 被加载,在 FragmentManager 中会有标记为 0 和 1 的缓存片段。现在在索引 0 处插入了一个页面(不要忘记调用notifyDataSetChanged()),旧索引 0 变为 1 并且 1 变为 2(这是使用方法发出的信号public int FragmentPageAdapter.getItemPosition(Object))。对于第 0 项,返回 POSITION_NONE(因为它是新位置),因此public Object instantiateItem(ViewGroup, int)为位置 0 调用该方法:

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 的缓存片段,并且您想要在位置 1 中的片段现在在位置 0,您想要在位置 2 中的片段现在在位置 1 并且在位置 2 您得到一个新片段返回FragmentPageAdapter.getItem(int) 是位置 1 的副本。

如何解决这个问题?我看到了很多关于 SO 的建议,包括:

  1. 始终从 FragmentPageAdapter.getItemPosition() https://stackoverflow.com/a/7386616/2351246返回 POSITION_NONE - 这个答案忽略了效率和内存管理(如果不清除缓存的片段,最终将无法工作)
  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 缓存中查找正确的片段。

于 2017-09-28T21:05:14.087 回答