我在更改屏幕方向后,需要从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
。我在OutterFragment
的onActivityCreated()
回调中实例化了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
保持不变,因此其中的所有内容也保持不变,包括引用等...因为托管pagerAdapter
的OutterFragment
仅被分离而不是销毁,然后重新附加)。可能与片段管理器有关,但我真的不知道。
我已经尝试过的事情:
尝试使用另一种技术删除我的进度片段,即在
onTaskSuccess()
回调上尝试将片段从片段管理器中删除,但没有起作用。我还尝试隐藏进度元素,而不是完全从片段管理器中删除它。这有效了50%,因为视图不再存在,但我得到了一个空白页面,所以这不是我正在寻找的。
我还尝试在屏幕方向更改后重新将
progressFragment
附加到片段管理器中,但也没有起作用。我还尝试在活动重新创建后先删除进度片段,然后再将其添加回片段管理器中,但没有起作用。
尝试在
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。
它确实有效!