所以我在屏幕方向改变后从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)
在库的代码中挖掘了一下之后,在这种情况下,片段的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()
,这将调用getItemPosition()
以获取当前位于currentFragments
数组中的片段。当然,对于progress元素,它返回POSITION_NONE
,因为我想删除此页面,因此这基本上会调用来自destroyItem()
的{{1}}回调。如果我不旋转屏幕,一切正常,但正如我所说,如果我在显示进度元素并且API调用尚未完成时旋转它,那么PagedSingleDataAdapter
回调将在之后被调用活动重新开始。
也许我还应该说我在另一个片段中托管destroyItem()
而不是活动,因此ViewPager
托管OutterFragment
。我正在ViewPager
pagerAdapter
回调onActivityCreated()
并使用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
实例化后删除了pagerAdapter
中的进度元素,并且活动被销毁并重新创建(屏幕方向已更改),我得到上述异常(OutterFragment
保持不变,所以其中的所有内容也保持不变,引用等...因为pagerAdapter
托管onTaskSuccess()
的{{1}}未被销毁,只会从活动中分离,然后重新附加)。可能它与片段管理器发生了一些事情,但我真的不知道是什么。
我已尝试过的内容:
尝试使用其他技术删除我的进度片段,即progressFragment
回调我试图从片段管理器中删除片段,但是没有用。
我还试图隐藏progress元素,而不是从片段管理器中完全删除它。这个工作50%,因为视图不再存在,但我有一个空页面,所以这不是我正在寻找的。 p>
在屏幕方向更改后,我还尝试(重新)将destroyItem()
附加到片段管理器,这也无效。
我还尝试删除,然后在重新创建活动后再将进度片段添加到片段管理器中,不起作用。
试图从onTaskSuccess()
回调手动调用@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);
}
}
(这真的非常难看)但是没有用。
对于这么长的帖子感到抱歉,但是我试图尽可能地解释这个问题,以便你们能够理解它。
任何解决方案,建议都非常感谢。
谢谢!
更新:解决方案 好的,所以这需要一段时间。问题是destroyItem()回调在进程片段上被调用了两次,一次是在屏幕方向改变时,然后在api调用结束后再次调用。这就是为什么例外。我找到的解决方案如下: 如果api调用完成与否,请继续跟踪并在此情况下销毁进度片段,代码如下。
onTaskSuccess()
然后在适配器的构造函数中将此apiCallFinished设置为false,并在{{1}}回调中设置为true。 它确实有效!
答案 0 :(得分: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。它确实有效!