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 中运行吗?