屏幕方向更改后从 ViewPager 的适配器中销毁项目 [英] Destroy item from the ViewPager's adapter after screen orientation changed

查看:10
本文介绍了屏幕方向更改后从 ViewPager 的适配器中销毁项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我在屏幕方向更改后从 ViewPager 销毁(删除)一页时遇到问题.我将尝试在以下几行中描述问题.

So I'm having a problem with destroying (removing) one page from the ViewPager after the screen orientation changed. I'll try to describe the problem in the following lines.

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

I'm using the FragmentStatePagerAdapter for the adapter of the ViewPager and a small interface which describes how an endless view pager should work. The idea behind of it is that you can scroll to the right till you reach the end of the ViewPager. If you can load more results from an API call, a progress page is displayed till the results come.

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

Everything fine till here, now the problem comes. If during this loading process, I rotate the screen (this will not affect the API call which is basically an AsyncTask), when the call returns, the app crashes giving me this exception:

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,这会引发该异常.

After digging a bit in the code of the library it seems that the mIndex data field of the fragment is less than 0 in this case, and this raises that exception.

这是寻呼机适配器的代码:

Here is the code of the pager adapter:

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 调用:

The onPageSelected() callback below, which basically starts the api call if needed:

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

现在,还值得一提的是 API 调用在传递结果后做了什么.这是回调:

Now, it is also worth to say what the API call does after delivering the results. Here is the callback:

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

    return true;
}

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

Ok, now because the setData() method invokes the notifiyDataSetChanged(), this will call the getItemPosition() for the fragments that are currently in the currentFragments array. Of course that for the progress element it returns POSITION_NONE since I want to delete this page, so this basically invokes the destroyItem() callback from the PagedSingleDataAdapter. If I don't rotate the screen, everything works OK, but as I said if I'm rotating it when the progress element is displayed and the API call hasn't finished yet, the destroyItem() callback will be invoked after the activity is restarted.

也许我还应该说我将 ViewPager 托管在另一个 Fragment 中,而不是在活动中,因此 OutterFragment 托管 ViewPager.我在 OutterFragmentonActivityCreated() 回调中实例化 pagerAdapter 并使用 setRetainInstance(true)这样当屏幕旋转时 pagerAdapter 保持不变(什么都不应该改变,对吧?),代码在这里:

Maybe I should also say that I'm hosting the ViewPager in another Fragment and not in an activity, so the OutterFragment hosts the ViewPager. I'm instantiating the pagerAdapter in the onActivityCreated() callback of the OutterFragment and using the setRetainInstance(true) so that when the screen rotates the pagerAdapter remains the same (nothing should be changed, right?), code here:

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

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

现在总结一下,问题是:

如果我在实例化后尝试从 ViewPager 中删除进度元素并且活动被销毁并重新创建(屏幕方向已更改),我会收到上述异常(pagerAdapter 保持不变,因此其中的所有内容也保持不变,引用等......因为承载 pagerAdapterOutterFragment 没有被破坏,只是从活动中分离出来,并且然后重新连接).可能它发生在片段管理器上,但我真的不知道是什么.

If I try to remove the progress element from the ViewPager after it was instantiated and the activity was destroyed and recreated (screen orientation changed) I get the above exception (the pagerAdapter remains the same, so everything inside of it also remains the same, references etc… since the OutterFragment which hosts the pagerAdapter is not destroyed is only detached from the activity and then re-attached). Probably it happens something with the fragment manager, but I really don't know what.

我已经尝试过的:

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

  1. Trying to remove my progress fragment using another technique i.e on the onTaskSuccess() callback I was trying to remove the fragment from the fragment manager, didn't work.

我还尝试隐藏进度元素,而不是将其从片段管理器中完全删除.这工作了 50%,因为视图不再存在,但我有一个空页面,所以这不是我真正想要的.

I also tried to hide the progress element instead of removing it completely from the fragment manager. This worked 50%, because the view was not there anymore, but I was having an empty page, so that's not really what I'm looking for.

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

I also tried to (re)attach the progressFragment to the fragment manager after the screen orientation changes, this also didn't work.

我还尝试在重新创建活动后将进度片段删除然后再次添加到片段管理器,但没有奏效.

I also tried to remove and then add again the progress fragment to the fragment manager after the activity was recreated, didn't work.

尝试从 onTaskSuccess() 回调中手动调用 destroyItem()(这真的非常难看),但没有成功.

Tried to call the destroyItem() manually from the onTaskSuccess() callback (which is really, really ugly) but didn't work.

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

Sorry guys for such a long post, but I was trying to explain the problem as best as I can so that you guys can understand it.

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

Any solution, recommendation is much appreciated.

谢谢!

更新:找到解决方案好的,所以这需要一段时间.问题是在进度片段上调用了 destroyItem() 回调两次,一次是在屏幕方向改变时,一次是在 api 调用完成后.这就是为什么例外.我找到的解决方案如下:继续跟踪 api 调用是否完成,并在这种情况下销毁进度片段,代码如下.

UPDATE: SOLUTION FOUND OK, so this took a while. The problem was that the destroyItem() callback was called twice on the progress fragment, once when the screen orientation changed and then once again after the api call finished. That's why the exception. The solution that I found is the following: Keep tracking if the api call finished or not and destroy the progress fragment just in this case, code below.

@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.而且真的很管用!

and then this apiCallFinished is set to false in the constructor of the adapter and to true in the onTaskSuccess() callback. And it really works!

推荐答案

UPDATE: SOLUTION FOUND 好的,所以这需要一段时间.问题是在进度片段上调用了 destroyItem() 回调两次,一次是在屏幕方向改变时,一次是在 api 调用完成后.这就是为什么例外.我找到的解决方案如下:继续跟踪api调用是否完成并在这种情况下销毁进度片段,代码如下.

UPDATE: SOLUTION FOUND OK, so this took a while. The problem was that the destroyItem() callback was called twice on the progress fragment, once when the screen orientation changed and then once again after the api call finished. That's why the exception. The solution that I found is the following: Keep tracking if the api call finished or not and destroy the progress fragment just in this case, code below.

@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.而且真的很管用!

and then this apiCallFinished is set to false in the constructor of the adapter and to true in the onTaskSuccess() callback. And it really works!

这篇关于屏幕方向更改后从 ViewPager 的适配器中销毁项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆