从FragmentStatePagerAdapter中移除片段

11

更新

经过一番激烈的争斗和SO用户的帮助,我终于解决了这个问题。这就是我的clearProgressUpTo现在的样子。

    public void clearProgressUpTo(int progress) {
    boolean deleted = false;

    for (int i = fragments.size() - 1; i > 0; i--) {
        int key = fragments.keyAt(i);
        if (key != progress) {
            fragments.delete(key);
            deleted = true;
        } else {
            break;
        }
    }

    if (deleted)
        notifyDataSetChanged(); //prevents recursive call (and IllegalStateException: Recursive entry to executePendingTransactions)

    while (mSavedState.size() > progress) {
        mSavedState.remove(mSavedState.size()-1);
    }

}
我在notifyDataSetChanged后执行此操作,原因是initiateItem会再次填充mSavedState。而我确信,在通知适配器后,适配器不再持有任何这些Fragments
如果您想这样做,请将您的mSaveState更改为protected,这样您就可以从扩展类(在我的情况下是VerticalAdapter)中获取它。
我忘了提到,我正在使用Adam Speakman的FragmentStatePagerAdapter修复程序来解决我的问题。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
我遇到的问题是需要完全删除一些在FragmentStatePagerAdapter中保存的Fragments
我已经对其进行了POSITION_NONE修复,但旧的fragments似乎仍然存在。
构造方面,由于我的ViewPager的构造方式不同寻常,所以我被迫在继承FragmentStatePagerAdapterVerticalAdater内部放置一个包含所有片段的SparseArray
public class VerticalAdapter extends FragmentStatePagerAdapter {

private SparseArray<Fragment> fragments = new SparseArray<Fragment>(); 
...

@Override
public Fragment getItem(int position) {
    return getFragmentForPosition(position);
}

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

问题

我已经创建了一个特殊的方法来清除我的 FragmentStatePagerAdapter 中某一点之前的所有 Fragments, 它工作得很好,但是问题是我无法清除 FragmentStatePagerAdapter 内部的 ArrayList。即使我清除了 SparseArrayVerticalAdapter 继承的 FragmentStatePagerAdapterArrayList 仍然持有我不想要的 Fragments。因此,当我创建新的 Fragments 时它们会保留旧的状态。

public void clearProgressUpTo(int progress) {
    boolean deleted = false;

    for (int i = fragments.size() - 1; i > 0; i--) {
        int key = fragments.keyAt(i);
        if (key != progress) {
            fragments.delete(key);
            deleted = true;
        } else {
            break;
        }
    }

    if (deleted)
        notifyDataSetChanged(); //prevents recursive call (and IllegalStateException: Recursive entry to executePendingTransactions)
}

如果您需要更多信息或代码,请在评论中告诉我,我将尽可能提供。

我不知道如何解决这个问题,所以任何建议都将不胜感激。

编辑

添加所请求的代码。

private Fragment getFragmentForPosition(int position) {
    return fragments.get(getKeyForPosition(position));
}

public int getKeyForPosition(int position) {
    int key = 0;
    for (int i = 0; i < fragments.size(); i++) {
        key = fragments.keyAt(i);
        if (i == position) {
            return key;
        }
    }
    return key;
}

请发布 getFragmentForPosition(position); - mmlooloo
已添加您请求的代码。 - JakubW
Adapter中唯一缺少的方法是setProgress,它填充了fragments数组。 为什么我知道它是旧状态?因为setProgress正在使用我的Fragmentsnewinstance重新加载整个fragments数组。 此外,ViewPager数组中的Fragments的ID与setProgress之前相同。 - JakubW
我认为我的问题与https://dev59.com/Y2kw5IYBdhLWcg3wrsdS有关。也许是因为我将我的“Fragments”保存在“SparseArray”中,所以“notifyDataSetChanged”没有更新我的数据。 - JakubW
这个问题在Viewpager2状态页适配器中修复了吗?有人可以确认一下吗? - Harshal Pudale
3个回答

7
FragmentStatePageAdapter会在销毁片段时保存其状态,因此当片段被重新创建时,保存的状态将应用于它。
片段状态存储在私有成员变量中。因此,我们不能扩展当前实现以添加删除已保存状态的功能。
我已克隆了当前的FragmentStatePagerAdapter实现,并包含了保存状态删除功能。这是Gist参考
   /**
     * Clear the saved state for the given fragment position.
     */
    public void removeSavedState(int position) {
        if(position < mSavedState.size()) {
            mSavedState.set(position, null);
        }
    }

在您的clearProgressUpTo方法中,每当您从稀疏数组中删除片段时,请调用此方法,以清除该片段的保存状态。

我昨天解决了我的问题,它与你的答案有关。 我从FragmentStatePagerAdapter中取出mSaveState,并在我要丢弃的元素上使用mSaveState.remove。 我会接受你的答案,因为它最接近解决这个问题。 - JakubW
嗨JakubW...你能分享一下你的答案吗? - amity

7
您的问题不是由于使用SparseArray引起的。实际上,您遇到了FragmentStatePagerAdapter的一个错误。我花了一些时间阅读了FragmentStatePagerAdapter、ViewPager和FragmentManagerImpl的源代码,然后找到了您的新片段具有旧片段状态的原因:
当FragmentStatePagerAdapter删除旧片段时,它保存了其状态,如果稍后将新片段添加到适配器中,则保存的状态将传递给相同位置的新片段。
我在Google上搜索了这个问题,并找到了一个解决方案,这是它的链接: http://speakman.net.nz/blog/2014/02/20/a-bug-in-and-a-fix-for-the-way-fragmentstatepageradapter-handles-fragment-restoration/ 源代码可以在这里找到: https://github.com/adamsp/FragmentStatePagerIssueExample/blob/master/app/src/main/java/com/example/fragmentstatepagerissueexample/app/FixedFragmentStatePagerAdapter.java 这个解决方案的关键是重写FragmentStatePagerAdapter并使用一个字符串标签来识别适配器中的每个片段。因为每个片段都有不同的标签,所以很容易识别新的片段,然后不要将保存的状态传递给新的片段。
最后,感谢Adam Speakman是该解决方案的作者。

在发布到SO之前,我已经尝试过这个修复bug的方法,但它并没有解决问题 :C 不过我还准备再试一次,并发布我的结果。 - JakubW
我正在尝试使用它,但目前问题仍然存在。不过它似乎为我修复了其他一些问题。 但是这个解决方案还有另一个问题。当我将我的“fragments”从“{1, 2, 3, 7}”扩展到“{1, 2, 3, 4, 7}”时,TAG没有插入新的“Fragment”(4)。奇怪的是,在屏幕旋转后它确实起作用了。 - JakubW
在插入新的Fragment(4)之后,它的TAG没有被插入进去,出现了问题。只有在屏幕旋转发生后才能正常工作。 - JakubW
好的,我利用 POSITION_NONE 修复了这个问题。虽然它并没有在错误修复中使用,但在我的情况下似乎是必需的。最糟糕的是,我的原始问题仍然存在,我无法摆脱它 :C - JakubW
1
@JakubW 是的,在你的情况下需要使用 POSITION_NONE,因为你的 Fragment 位置可能会发生变化。 - Lei Guo
显示剩余5条评论

0

当您调用notifyDataSetChanged()时,适配器开始使用destroyItem(ViewGroup container, int position, Object object)删除其片段。

但是,每个片段的实例状态由FragmentStatePagerAdapter在内部保留,以便稍后在instantiateItem(ViewGroup container, int position)中使用。

只需保存要删除的项的位置并调用notifyDataSetChanged()。在最后一项被销毁后,从FragmentStatePagerAdapter检索所需的保存实例状态,并替换为null。

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);

    if(position == getCount() - 1 && i >= 0){
        Bundle state = (Bundle) saveState();
        Fragment.SavedState[] states = (Fragment.SavedState[]) state.getParcelableArray("states");
        if (states != null)
            states[i] = null;
        i = -1;
        restoreState(state, ClassLoader.getSystemClassLoader());
    }

注意: getItemPosition(Object object) 必须返回 PagerAdapter.POSITION_NONE


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