ViewPager
そのため、画面の向きが変更された後、 から 1 ページを破棄 (削除) することに問題があります。次の行で問題を説明してみます。
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
もちろん、POSITION_NONE
このページを削除したいので、進行状況要素に対してはそれが返されるので、これは基本的にdestroyItem()
からコールバックを呼び出しますPagedSingleDataAdapter
。画面を回転させなければすべて正常に動作しますが、前述のように、progress 要素が表示されていて API 呼び出しがまだ終了していないときに画面を回転させると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()
、フラグメント マネージャーからフラグメントを削除しようとしていたコールバックで、うまくいきませんでした。また、進行状況要素をフラグメント マネージャーから完全に削除するのではなく、非表示にしようとしました。ビューがもう存在しないため、これは 50% 機能しましたが、空のページがあったため、実際に探しているものではありません。
また、画面の向きが変更された後、フラグメント マネージャーに を (再) 接続しようとしまし
progressFragment
たが、これも機能しませんでした。また、アクティビティが再作成された後、進行状況フラグメントを削除してフラグメントマネージャーに再度追加しようとしましたが、機能しませんでした。
destroyItem()
コールバックから手動でを呼び出そうとしましたonTaskSuccess()
が (これは本当に、本当に醜いです)、機能しませんでした。
とても長い投稿で申し訳ありませんが、皆さんが理解できるように、問題をできる限り説明しようとしていました.
任意のソリューション、推奨事項は大歓迎です。
ありがとう!
更新: 解決策が見つかっ たため、これにはしばらく時間がかかりました。問題は、progress フラグメントで destroyItem() コールバックが 2 回呼び出されることでした。それが例外の理由です。私が見つけた解決策は次のとおりです。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 に設定されます。そして、それは本当にうまくいきます!