如何将Fragment添加到FragmentPagerAdapter的第一个位置

3

我正在使用FragmentPagerAdapter和ViewPager添加自定义片段,通过左右滑动来浏览列表中的下一个片段。请注意:我还从我的MainActivity发送了一些额外的数据,这些数据基于JSON响应通过bundle传递。

public class MyPagerAdapter extends FragmentPagerAdapter implements Serializable {

 public List<Fragment> fragments;
 public FragmentManager fm;

 public MyPagerAdapter(FragmentManager fm) {
  super(fm);
  this.fm = fm;
  this.fragments = new ArrayList<Fragment>(); 
 }
 
 @Override
 public Fragment getItem(int position) {
  return fragments.get(position);
 }


 @Override
 public int getCount() {
  return fragments.size();
 }

}

只要我使用 Fragment 添加新的片段,一切都正常运作。

MyPagerAdapter pageAdapter = new MyPagerAdapter(getSupportFragmentManager());

pager = (ViewPager)findViewById(R.id.myViewPager);

pageAdapter.fragments.add(new CustomFragment());

pager.setAdapter(pageAdapter);

但是我找不到一种合适的方法来将Fragment添加到列表的开头并进行滑动后退。

我尝试了两种方法:

pageAdapter.fragments.add(0, new CustomFragment());

除了将FragmentPagerAdapters的List更改为LinkedList并使用

pageAdapter.fragments.addFirst(new CustomFragment());

然后通过使用刷新适配器

pageAdapter.notifyDataSetChanged();

我一直在遇到以下异常:

java.lang.IllegalStateException: 无法更改片段 CustomFragment{2ead9520 #10 id=0x7f0a0002 android:switcher:2131361794:10} 的标签:原先为 android:switcher:2131361794:10,现在为 android:switcher:2131361794:11


为每个页面创建一个新实例。 - Krupal Shah
@Krupal:我以为我已经创建了CustomFragment()的新实例。 - Christopher Hackl
4个回答

3
到目前为止,没有人谈论过的关键方法是public int getItemPosition(Object),它用于在移动后重新映射片段到页面,以及public long getItemId(int position),必须由重新排序片段的pager适配器覆盖。默认实现使用页面的位置作为id,因此重新排序会使FragmentPagerAdapter混乱。
(我不包括Serializable接口,因为它与回答如何在FragmentPagerAdapter中重新排序片段的问题无关。)
public class MyPagerAdapter extends FragmentPagerAdapter {

    public List<Fragment> fragments;
    public FragmentManager fm;

    public MyPagerAdapter(FragmentManager fm) {
        super(fm);
        this.fm = fm;
        this.fragments = new ArrayList<Fragment>(); 
    }

    void addFragmentAtPosition(int position, Fragment f) {
        if(position == fragments.size())
            fragments.add(f);
        else
            fragments.add(position, f);
        notifyDataSetChanged();
    }

    void removeFragmentAtPosition(int position) {
        Fragment f = fragments.remove(position);
        if(f != null)
            fm.beginTransaction().remove(f).commit();
        notifyDataSetChanged();
    }

    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }

    @Override
    public int getItemPosition(Object object){
        /*
         * called when the fragments are reordered to get the
         * changes.
         */
        int idx = fragments.indexOf(object);
        return idx < 0 ? POSITION_NONE : idx;
    }

    @Override
    public long getItemId(int position) {
        /*
         * map to a position independent ID, because this
         * adapter reorders fragments
         */
        return System.identityHashCode(fragments.get(position));
    }
}

关键的更新是对public int getItemPosition(Object)public long getItemId(int)方法的重写。这使得FragmentPagerAdapter能够重新定位现有的片段,并正确识别FragmentManager缓存中的现有活动片段。

那么FragmentStatePagerAdapter怎么办?它没有getItemId方法。 - Daniel
我已经实现了一段时间了。FragmentStatePagerAdapter仅加载视图中的片段以及前后一个片段,因此位置对于该适配器并不重要。例如,您可以拥有一个FragmentStatePagerAdapter,它显示书的页面,如果您正在查看第50页,则会加载第49、50和51页。当您滑动到第51页时,将实例化第52页并卸载第49页。对象的位置意义不大,插入到第1页不会影响FragmentStatePagerAdapter的当前状态。即插入方法必须不同。 - BitByteDog

1
你不应该以这种方式创建和添加片段。相反,只需在getItem中实例化片段,适配器会负责使用它们。只需执行以下操作:
@Override
public Fragment getItem(int position) {
    Fragment fragment = new CustomFragment();
    fragments.add(fragment)
    return fragment
}

1
我建议您不要保留片段的引用列表,因为这是不必要的,而且会有内存泄漏的风险。我的做法是只在需要时创建片段,像这样:
@Override
public Fragment getItem(int position) {
    Fragment fragment = new MyFragment();
    return fragment;
}

为了解决您的问题,您应该根据您的需求创建片段。例如,如果您有不同类实例的片段,比如MyFragment的一个实例,YourFragment的另一个实例等等,只需保留一个列表,指示哪种类型的片段占据了该位置。 例如:
myListMap = new HashMap<Integer, Integer>();
myListMap.put(position, type);

然后动态创建片段:
@Override
public Fragment getItem(int position) {
    Fragment fragment = null;
    int type = ...find fragment type in that position ....
    if(type == MYFRAGMENTTYPE) {
        fragment = new MyFragment();
    }
    return fragment;
}

我不确定我理解你的回答。"我建议您不要保留对片段的引用列表,因为这是不必要的,并且会导致内存泄漏。" 那我是如何保留对片段的引用呢? - Christopher Hackl
我也编辑了我的问题... 我正在将从JSON响应接收到的数据发送到MainActivity中的片段,但我不确定如何按照您建议的方式执行此操作。 - Christopher Hackl

0

不知道你是否还需要答案,但我之前也尝试过类似的操作,并找到了解决方案 :)

你之所以收到这个异常是因为你的 getItem 函数。你正在返回函数接收到的位置上的片段,而这个位置始终是数组的最后一个位置,因为这会对应于“典型”用法中最后添加的片段。

在你的情况下,你想在第一个位置添加一个新的 Fragment,所以你的 getItem 将会返回两次相同的片段并抛出异常。

为了避免这种情况发生,你需要在你的 Adapter 中创建一个公共函数并传递你正在添加的片段的索引,然后返回这个特定的片段。

附注:我现在已经只使用 Kotlin 开发了大约 6 个月,所以可能有一些拼写错误。

public int newFragmentIndex = 0;
public List<Fragment> fragments;

...

public void addFragmentAt(int index, Fragment fragment) {
    newFragmentIndex = index;
    pageAdapter.fragments.add(index, fragment);
    notifyDataSetChanged();
}

public void addFragment(Fragment fragment) {
    newFragmentIndex = fragments.size();
    pageAdapter.fragments.add(fragment);
    notifyDataSetChanged();
}

...

@Override
public Fragment getItem(int position) {
    return fragments.get(newFragmentIndex);
}


要从您的Activity中调用此函数,请将
pageAdapter.fragments.add(new CustomFragment());
更改为
pageAdapter.fragments.addFragment(new CustomFragment());

并且
pageAdapter.fragments.add(0, new CustomFragment());
更改为
pageAdapter.fragments.addFragmentAt(0, new CustomFragment());


希望这能帮助你解决问题!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接