Android 在 FragmentPagerAdapter 中更改片段顺序

Posted

技术标签:

【中文标题】Android 在 FragmentPagerAdapter 中更改片段顺序【英文标题】:Android changing fragment order inside FragmentPagerAdapter 【发布时间】:2012-09-15 07:58:02 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

【参考方案2】:

接受的答案是非最佳答案。从int getItemPosition(Object) 返回POSITION_NONE 只会破坏任何高效片段管理的希望,需要重新实例化所有片段。它还忽略了另一个问题。 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(因为它是新位置),因此为位置 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 的 Fragment 现在在位置 0,您想要在位置 2 的 Fragment 现在在位置 1,在位置 2 你得到一个新的FragmentPageAdapter.getItem(int) 返回的片段是位置 1 的副本。

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

    始终从 FragmentPageAdapter.getItemPosition() https://***.com/a/7386616/2351246 返回 POSITION_NONE - 此答案忽略了效率和内存管理(如果不清除缓存的片段,最终将无法工作) 跟踪片段标签https://***.com/a/12104399/2351246,这依赖于可能更改的实现细节。 最糟糕的是,通过对 FragmentPageAdapter 实现 https://***.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 缓存中查找正确的片段,即使它移动页面。

【讨论】:

以上是关于Android 在 FragmentPagerAdapter 中更改片段顺序的主要内容,如果未能解决你的问题,请参考以下文章

Android - 在 Android 1.6 中开发的应用程序可以在 Android 2.0 中运行吗?

如何在Android中启动JAVA程序

android中怎么设置组件在LinearLayout中居中

如何在android源代码中打印出日志

求教怎么在android的Logcat中输出日志

如何在Android中取得当前进程名