在屏幕方向改变后,从ViewPager的适配器中删除项目

25

我在更改屏幕方向后,需要从ViewPager中删除一个页面,但是遇到了问题。以下是对问题的描述:

我为ViewPager的适配器使用FragmentStatePagerAdapter和一个小接口,描述了无限滚动视图页应该如何工作。其背后的想法是,您可以向右滚动,直到达到ViewPager的末端。如果可以从API调用中加载更多结果,则会显示进度页面,直到结果出现。

到这里一切都好,现在问题来了。如果在此加载过程中旋转屏幕(这不会影响基本上是AsyncTask的API调用),当调用返回时,应用程序将崩溃并给出以下异常:

E/AndroidRuntime(13471): java.lang.IllegalStateException: Fragment ProgressFragment{42b08548} is not currently in the FragmentManager
E/AndroidRuntime(13471):    at android.support.v4.app.FragmentManagerImpl.saveFragmentInstanceState(FragmentManager.java:573)
E/AndroidRuntime(13471):    at android.support.v4.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:136)
E/AndroidRuntime(13471):    at mypackage.OutterFragment$PagedSingleDataAdapter.destroyItem(OutterFragment.java:609)

在深入查看库的代码后,发现在此情况下片段的mIndex数据字段小于0,从而引发异常。

这是分页适配器的代码:

static class PagedSingleDataAdapter extends FragmentStatePagerAdapter implements
        IEndlessPagerAdapter {

    private WeakReference<OutterFragment> fragment;
    private List<DataItem> data;
    private SparseArray<WeakReference<Fragment>> currentFragments = new SparseArray<WeakReference<Fragment>>();

    private ProgressFragment progressElement;

    private boolean isLoadingData;

    public PagedSingleDataAdapter(SherlockFragment fragment, List<DataItem> data) {
        super(fragment.getChildFragmentManager());
        this.fragment = new WeakReference<OutterFragment>(
                (OutterFragment) fragment);
        this.data = data;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object item = super.instantiateItem(container, position);
        currentFragments.append(position, new WeakReference<Fragment>(
                (Fragment) item));
        return item;
    }

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

    @Override
    public Fragment getItem(int position) {
        if (isPositionOfProgressElement(position)) {
            return getProgessElement();
        }

        WeakReference<Fragment> fragmentRef = currentFragments.get(position);
        if (fragmentRef == null) {
            return PageFragment.newInstance(args); // here I'm putting some info
                                                // in the args, just deleted
                                                // them now, not important
        }

        return fragmentRef.get();
    }

    @Override
    public int getCount() {
        int size = data.size();
        return isLoadingData ? ++size : size;
    }

    @Override
    public int getItemPosition(Object item) {
        if (item.equals(progressElement) && !isLoadingData) {
            return PagerAdapter.POSITION_NONE;
        }
        return PagerAdapter.POSITION_UNCHANGED;
    }

    public void setData(List<DataItem> data) {
        this.data = data;
        notifyDataSetChanged();
    }

    @Override
    public boolean isPositionOfProgressElement(int position) {
        return isLoadingData && position == data.size();
    }

    @Override
    public void setLoadingData(boolean isLoadingData) {
        this.isLoadingData = isLoadingData;
    }

    @Override
    public boolean isLoadingData() {
        return isLoadingData;
    }

    @Override
    public Fragment getProgessElement() {
        if (progressElement == null) {
            progressElement = new ProgressFragment();
        }
        return progressElement;
    }

    public static class ProgressFragment extends SherlockFragment {

        public ProgressFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {

            TextView progressView = new TextView(container.getContext());
            progressView.setGravity(Gravity.CENTER_HORIZONTAL
                    | Gravity.CENTER_VERTICAL);
            progressView.setText(R.string.loading_more_data);
            LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT,
                    LayoutParams.FILL_PARENT);
            progressView.setLayoutParams(params);

            return progressView;
        }
    }
}

以下是 onPageSelected() 回调函数,它的作用是在需要时启动 API 调用:

 @Override
    public void onPageSelected(int currentPosition) {
        updatePagerIndicator(currentPosition);
        activity.invalidateOptionsMenu();
        if (requestNextApiPage(currentPosition)) {
            pagerAdapter.setLoadingData(true);
            requestNextPageController.requestNextPageOfData(this);
        }

现在,同样值得一提的是API调用在返回结果后所做的事情。以下是回调函数:

@Override
public boolean onTaskSuccess(Context arg0, List<DataItem> result) {
    data = result;
    pagerAdapter.setLoadingData(false);
    pagerAdapter.setData(result);
    activity.invalidateOptionsMenu();

    return true;
}

现在,因为setData()方法调用了notifiyDataSetChanged(),这将调用currentFragments数组中当前片段的getItemPosition()。当然,对于进度元素,它返回POSITION_NONE,因为我想要删除该页,因此这基本上会从PagedSingleDataAdapter调用destroyItem()回调。如果我不旋转屏幕,一切都正常工作,但是如我所说,如果我在显示进度元素且API调用尚未完成时旋转它,则destroyItem()回调将在重新启动活动后被调用。

也许我还应该说一下,我将ViewPager托管在另一个片段中而不是活动中,因此OutterFragment托管ViewPager。我在OutterFragmentonActivityCreated()回调中实例化了pagerAdapter,并使用setRetainInstance(true),以便在屏幕旋转时pagerAdapter保持不变(不应更改任何内容,对吧?),代码如下:

if (pagerAdapter == null) {
    pagerAdapter = new PagedSingleDataAdapter(this, data);
}
pager.setAdapter(pagerAdapter);

if (savedInstanceState == null) {
    pager.setOnPageChangeListener(this);
    pager.setCurrentItem(currentPosition);
}

总结一下,问题是:

如果在实例化并销毁并重新创建活动(屏幕方向更改)后从ViewPager中删除进度元素,则会出现上述异常(pagerAdapter保持不变,因此其中的所有内容也保持不变,包括引用等...因为托管pagerAdapterOutterFragment仅被分离而不是销毁,然后重新附加)。可能与片段管理器有关,但我真的不知道。

我已经尝试过的事情:

  1. 尝试使用另一种技术删除我的进度片段,即在onTaskSuccess()回调上尝试将片段从片段管理器中删除,但没有起作用。

  2. 我还尝试隐藏进度元素,而不是完全从片段管理器中删除它。这有效了50%,因为视图不再存在,但我得到了一个空白页面,所以这不是我正在寻找的。

  3. 我还尝试在屏幕方向更改后重新将progressFragment附加到片段管理器中,但也没有起作用。

  4. 我还尝试在活动重新创建后先删除进度片段,然后再将其添加回片段管理器中,但没有起作用。

  5. 尝试在onTaskSuccess()回调中手动调用destroyItem()(这真的非常丑陋),但没有起作用。

对不起,大家看了这么长一篇文章,但我尽力将问题解释得尽可能清楚详细,以便大家能够理解。

非常感谢任何解决方案和建议。

谢谢!

更新:已找到解决方案 好吧,这花了一些时间。问题是,在屏幕方向更改后和api调用完成后,销毁了两次进度片段的destroyItem()回调。这就是为什么会出现异常。我发现的解决方案如下: 跟踪api调用是否完成并仅在此情况下销毁进度片段,如下所示。

@Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            if (object.equals(progressElement) && apiCallFinished == true) {
                apiCallFinished = false;
                currentFragments.put(position, currentFragments.get(position + 1));
                super.destroyItem(container, position, object);
            } else if (!(object.equals(progressElement))) {
                currentFragments.put(position, null);
                super.destroyItem(container, position, object);
            }
        }

然后在适配器构造函数中将apiCallFinished设置为false,并在onTaskSuccess()回调中将其设置为true。

它确实有效!


也许这是一个愚蠢的问题,但是在旋转屏幕后,您是否真的将片段重新添加到viewPager中?您的活动在方向更改时会被销毁。 - Stefan de Bruijn
啊,好的,是的,我正在添加它们,但忘记添加代码了 :)。谢谢你指出这一点,我会稍微编辑一下我的帖子。干杯! - Radu Comaneci
尝试在onCreate中使用.addTab()实际添加您的选项卡。您的问题只出现在progressFragment还是其他地方也有问题? - Stefan de Bruijn
问题只出现在这个progressFragment上,其他页面都没有问题,它们都是PagerFragment实例。然而你提出的建议并不可行,因为我没有使用TabsAdapter,所以没有addTab()方法,但还是谢谢你的建议。 - Radu Comaneci
啊,对不起,我会更新我的帖子,所以如果需要新数据,setLoadingTrue() 将在 onPageSelected() 回调中完成,即如果视图页面到达末尾但仍然可以从 API 获取数据。感谢您的建议,但这并不是情况,我找到了答案,谢谢! - Radu Comaneci
请添加答案并标记为已解决。 - Warpzit
1个回答

4

更新:问题已解决 好的,这花了一些时间。问题在于destroyItem()回调在进度片段上被调用了两次,一次是屏幕方向改变时,另一次是api调用完成后。这就是为什么会出现异常。我找到的解决方案如下:跟踪api调用是否完成,只有在这种情况下才销毁进度片段,以下是代码。

@Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            if (object.equals(progressElement) && apiCallFinished == true) {
                apiCallFinished = false;
                currentFragments.put(position, currentFragments.get(position + 1));
                super.destroyItem(container, position, object);
            } else if (!(object.equals(progressElement))) {
                currentFragments.put(position, null);
                super.destroyItem(container, position, object);
            }
        }

然后在适配器的构造函数中将apiCallFinished设置为false,在onTaskSuccess()回调函数中将其设置为true。这真的很有效!


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