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

所以我在屏幕方向改变之后从ViewPager销毁(移除)一个页面时遇到问题。 我会尝试在下面的行中描述这个问题。

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

一切都很好,直到现在,问题来了。 如果在这个加载过程中,我旋转屏幕(这不会影响基本上是AsyncTask的API调用),当调用返回时,应用程序崩溃给我这个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 ,这引发了这个exception。

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

 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()callback,如果需要,基本上启动api调用:

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

现在,在交付结果之后,API调用还是值得的。 这是callback:

 @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() 。 当然,对于progress元素,它返回POSITION_NONE因为我想删除这个页面,所以这基本上调用了PagedSingleDataAdapterdestroyItem()callback。 如果我不旋转屏幕,一切正常,但正如我所说,如果我正在旋转它,当progress元素显示和API调用还没有完成, destroyItem()callback将被调用后的活动重新启动。

也许我应该说,我在另一个片段而不是在一个活动中托pipeViewPager ,所以OutterFragment托pipeViewPager 。 我正在实例化pagerAdapteronActivityCreated()callback中的OutterFragment并使用setRetainInstance(true)以便在屏幕旋转时pagerAdapter保持不变(不应该改变,对吗?),代码如下:

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

总结现在的问题是:

如果我试图从实例化ViewPager删除进度元素和活动被销毁并重新创build(屏幕方向更改),我得到上述exception( pagerAdapter保持不变,所以里面的一切也保持不变,引用等等,因为承载pagerAdapter不被销毁,只从活动中分离出来,然后重新连接)。 可能它发生在片段pipe理器的东西,但我真的不知道是什么。

我已经尝试过了:

  1. 尝试删除我的进度片段使用另一种技术,即在onTaskSuccess()callback我试图从片段pipe理器中删除片段,没有工作。

  2. 我也尝试隐藏进度元素,而不是完全从片段pipe理器中删除它。 这工作了50%,因为视图不在了,但我有一个空的页面,所以这不是我所期待的。

  3. 在屏幕方向改变之后,我也尝试(重新)将progressFragment附加到片段pipe理器,这也是行不通的。

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

  5. 尝试从onTaskSuccess()callback手动调用destroyItem() (这真的很丑),但没有奏效。

对不起,这么长的职位的人,但我试图尽可能解释这个问题,以便你们能理解。

任何解决scheme,build议非常感谢。

谢谢!

更新:解决schemefind了 ,所以这花了一段时间。 问题是destroyItem()callback在进度片段上被调用两次,一旦屏幕方向改变,然后在api调用结束后再次调用。 这就是例外的原因。 我find的解决scheme如下:保持跟踪,如果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()callback中将其设置为true。 它真的有用!

Solutions Collecting From Web of "屏幕方向更改后,从ViewPager的适配器中销毁项目"

更新:解决schemefind了 ,所以这花了一段时间。 问题是destroyItem()callback在进度片段上被调用两次,一旦屏幕方向改变,然后在api调用完成后再次调用。 这就是例外的原因。 我find的解决scheme如下:保持跟踪,如果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()callback中将其设置为true。 它真的有用!