更改屏幕方向后,从ViewPager的适配器中销毁项目

Rad*_*eci 24 android android-viewpager android-pageradapter fragmentstatepageradapter

所以我ViewPager在屏幕方向改变后从屏幕中删除(删除)一个页面时出现问题.我将尝试在以下几行中描述问题.

我正在使用FragmentStatePagerAdapter适配器ViewPager和一个小接口来描述无限视图寻呼机应该如何工作.它背后的想法是你可以滚动到右边,直到你到达结束ViewPager.如果您可以从API调用加载更多结果,则会显示进度页面,直到结果出现.

一切都很好,直到现在,问题来了.如果在这个加载过程中,我旋转屏幕(这不会影响API调用,这基本上是一个AsyncTask),当调用返回时,应用程序崩溃给我这个异常:

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)
Run Code Online (Sandbox Code Playgroud)

在库的代码中挖掘了一下之后,似乎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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

onPageSelected()下面的回调,如果需要,它基本上启动api调用:

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

现在,还有必要说明在传递结果后API调用的作用.这是回调:

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

    return true;
}
Run Code Online (Sandbox Code Playgroud)

好的,现在因为该setData()方法调用了notifiyDataSetChanged(),这将调用getItemPosition()当前在currentFragments数组中的片段.当然,POSITION_NONE因为我想要删除这个页面,所以它返回的progress元素,所以这基本上调用了destroyItem()来自的回调PagedSingleDataAdapter.如果我不旋转屏幕,一切正常,但正如我所说,如果我在显示进度元素并且API调用尚未完成时旋转它,则destroyItem()在重新启动活动后将调用回调.

也许我还应该说我ViewPager在另一个片段中托管而不是在活动中,所以OutterFragment主持人ViewPager.我实例化pagerAdapteronActivityCreated()回调OutterFragment并使用setRetainInstance(true),这样,当屏幕旋转pagerAdapter位置保持不变(什么都不改变,对吧?),代码:

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

if (savedInstanceState == null) {
    pager.setOnPageChangeListener(this);
    pager.setCurrentItem(currentPosition);
}
Run Code Online (Sandbox Code Playgroud)

总结一下,问题是:

如果我尝试从ViewPager实例化后删除进度元素并且活动被销毁并重新创建(屏幕方向已更改)我得到上述异常(pagerAdapter保持相同,因此其中的所有内容也保持不变,引用等...因为未被销毁的OutterFragment主机pagerAdapter仅与活动分离,然后重新附加).可能它与片段管理器发生了一些事情,但我真的不知道是什么.

我已经尝试过的:

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

  2. 我还试图隐藏progress元素,而不是从片段管理器中完全删除它.这个工作率为50%,因为视图不再存在,但我有一个空页面,所以这不是我正在寻找的.

  3. 我也尝试(重新)progressFragment在屏幕方向改变后附加到片段管理器,这也没有用.

  4. 我还尝试删除,然后在重新创建活动后再次将进度片段添加到片段管理器,不起作用.

  5. 试图destroyItem()onTaskSuccess()回调中调用手动(这真的非常难看)但是没有用.

很抱歉这么长的帖子,但我试图尽可能地解释这个问题,以便你们能够理解它.

任何解决方案,建议非常感谢.

谢谢!

更新:解决方案找到了 ,所以这需要一段时间.问题是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);
            }
        }
Run Code Online (Sandbox Code Playgroud)

然后这个apiCallFinished在适配器的构造函数中设置为false,在onTaskSuccess()回调中设置为true .它确实有效!

Rad*_*eci 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);
            }
        }
Run Code Online (Sandbox Code Playgroud)

然后这个 apiCallFinished 在适配器的构造函数中设置为 false,并在 onTaskSuccess() 回调中设置为 true。而且它确实有效!