web-dev-qa-db-ja.com

画面の向きが変更された後、ViewPagerのアダプターからアイテムを破棄する

そのため、画面の向きが変わった後、ViewPagerから1ページを破棄(削除)する際に問題が発生しています。次の行で問題を説明しようとします。

FragmentStatePagerAdapterのアダプターにViewPagerを使用し、エンドレスビューページャーの動作方法を説明する小さなインターフェイスを使用しています。その背後にある考え方は、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()を呼び出します。もちろん、このページを削除したいので、progress要素では_POSITION_NONE_を返します。したがって、これは基本的にPagedSingleDataAdapterからdestroyItem()コールバックを呼び出します。画面を回転させない場合、すべてが正常に機能しますが、進行要素が表示され、API呼び出しがまだ終了していないときに回転させている場合、destroyItem()コールバックはアクティビティの再起動後に呼び出されます。

また、アクティビティではなく別のフラグメントでViewPagerをホストしているため、OutterFragmentViewPagerをホストしているとも言えます。 pagerAdapteronActivityCreated()コールバックでOutterFragmentをインスタンス化し、setRetainInstance(true)を使用して、画面がpagerAdapterは同じままです(何も変更しないでください)?

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

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

要約すると、[〜#〜] problem [〜#〜]は次のとおりです。

ViewPagerがインスタンス化され、アクティビティが破棄および再作成された後(画面の向きが変更された)からprogress要素を削除しようとすると、上記の例外が発生します(pagerAdapterは変わりません。 OutterFragmentをホストするpagerAdapterは破棄されないため、その中のすべても参照、…などが同じままです。アクティビティから切り離されてから再接続されます)。おそらくフラグメントマネージャーで何かが起こりますが、私は実際には何を知りません。

私がすでに試したこと:

  1. 別の手法、つまりonTaskSuccess()コールバックを使用して進行中のフラグメントを削除しようとしても、フラグメントマネージャからフラグメントを削除しようとしていたが、機能しませんでした。

  2. フラグメントマネージャーから進行要素を完全に削除するのではなく、非表示にすることも試みました。ビューはもう存在していなかったので、これは50%でしたが、私は空のページを持っていたので、それは本当に私が探しているものではありません。

  3. また、画面の向きが変わった後にフラグメントマネージャーにprogressFragmentを(再)接続しようとしましたが、これも機能しませんでした。

  4. また、アクティビティを再作成した後、進行状況フラグメントを削除してからフラグメントマネージャに再度追加しようとしましたが、機能しませんでした。

  5. destroyItem()コールバックから手動でonTaskSuccess()を呼び出そうとしましたが(実際には非常に見苦しい)、動作しませんでした。

このような長い投稿については申し訳ありませんが、皆さんが理解できるように、できる限り最善の方法で問題を説明しようとしていました。

任意のソリューション、推奨事項は大歓迎です。

ありがとう!

PDATE:SOLUTION FOUND OKなので、これには時間がかかりました。問題は、progressフラグメントでdestroyItem()コールバックが2回呼び出されることでした。1回は画面の向きが変わったとき、もう1回は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に設定されます。そして、それは本当に機能します!

24
Radu Comaneci

PDATE:SOLUTION FOUND OKなので、これには時間がかかりました。問題は、progressフラグメントでdestroyItem()コールバックが2回呼び出されることでした。1回は画面の向きが変わったとき、もう1回は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に設定されます。そして、それは本当に機能します!

4
Radu Comaneci