ViewPagerとフラグメント—フラグメントの状態を保存する正しい方法は何ですか?


492

フラグメントは、UIロジックをいくつかのモジュールに分離するのに非常に適しているようです。しかし、ViewPagerそのライフサイクルとともに、私にはまだ霧がかかっています。だからグルの考えはひどく必要です!

編集する

以下のばかげた解決策を参照してください;-)

範囲

メインアクティビティにはViewPagerフラグメントがあります。これらのフラグメントは、他の(サブメイン)アクティビティに対して少し異なるロジックを実装できるため、フラグメントのデータはアクティビティ内のコールバックインターフェイスを介して入力されます。そして、すべては最初の起動で正常に動作しますが、!...

問題

アクティビティが再作成されたとき(たとえば、方向が変更されたとき)ViewPagerのフラグメントを作成します。コード(以下にあります)は、アクティビティが作成されるたびに、ViewPagerフラグメントと同じ新しいフラグメントアダプターを作成しようとする(多分これが問題である)と述べていますが、FragmentManagerはすでにこれらのフラグメントをどこかに(どこに?)格納しています。それらのレクリエーションメカニズムを開始します。したがって、レクリエーションメカニズムは、アクティビティの実装されたメソッドを介してデータを開始するためのコールバックインターフェース呼び出しで「古い」フラグメントのonAttach、onCreateViewなどを呼び出します。ただし、このメソッドは、アクティビティのonCreateメソッドで作成された、新しく作成されたフラグメントを指します。

問題

多分私は間違ったパターンを使用していますが、Android 3 Proの本でさえそれについてあまり詳しくありません。だから、してください、私に一から二パンチを与え、それを正しい方法を行う方法を指摘しています。どうもありがとう!

コード

主な活動

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity、別名ヘルパー

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

アダプタ

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

断片

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

解決

ばかげた解決策は、(ホストアクティビティの)onSaveInstanceState内にフラグメントを保存し、getFragmentを介してonCreate内に取得することです。しかし、私はまだそれがそのように機能するべきではないという奇妙な気持ちを持っています...以下のコードを参照してください:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}

1
私は今疑問に思います:非常に異なるアプローチを使用するか、onCreated()で使用するためにonSavedInstancestateを介してメインアクティビティ(ダッシュボード)のフラグメントを保存しようとするべきですか?これらのフラグメントを保存してonCreateのバンドルから取得する適切な方法はありますか?彼らは包みそうにないようです...
Oleksii Malovanyi '31 / 10/31

2
2番目のアプローチは機能します。「ソリューション」を参照してください。しかし、それは見苦しいコードのようですよね。
Oleksii Malovanyi 2011年

1
Androidタグ(詳細はmeta.stackexchange.com/questions/100529/…)をクリーンアップするために、ソリューションを回答として投稿し、それを選択されたものとしてマークしますか?そうすれば、それは未回答の質問として表示されません:)
Alexander Lucas

1
ええ、大丈夫だと思います。私よりも良いものを望んでいた...
Oleksii Malovanyi

1
ばかげたソリューションはうまくいきますか?それは...私にはnullポインタ例外を与える
ジェン劉

回答:


451

FragmentPagerAdapterがフラグメントをFragmentManagerに追加するとき、フラグメントが配置される特定の位置に基づいて特別なタグを使用します。FragmentPagerAdapter.getItem(int position)その位置のフラグメントが存在しない場合にのみ呼び出されます。回転後、Androidはこの特定の位置のフラグメントをすでに作成/保存していることに気づくためFragmentManager.findFragmentByTag()、新しい位置を作成するのではなく、で再接続を試みます。これらすべては、を使用するFragmentPagerAdapterと無料で提供されますgetItem(int)。そのため、メソッド内にフラグメント初期化コードを含めるのが一般的です。

を使用していなくてもFragmentPagerAdapter、で毎回新しいフラグメントを作成することはお勧めできませんActivity.onCreate(Bundle)。お気づきのように、フラグメントがFragmentManagerに追加されると、回転後に再作成されるため、再度追加する必要はありません。そうすることは、フラグメントを操作するときのエラーの一般的な原因です。

フラグメントを操作するときの通常のアプローチは次のとおりです。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

を使用する場合FragmentPagerAdapter、フラグメント管理をアダプターに放棄するため、上記の手順を実行する必要はありません。デフォルトでは、現在の位置の前後にある1つのフラグメントのみがプリロードされます(ただし、FragmentStatePagerAdapter)。これはViewPager.setOffscreenPageLimit(int)によって制御されます。このため、アダプタの外部にあるフラグメントのメソッドを直接呼び出すことは、それらが生きていなくてもよいため、有効であるとは限りません。

長い話を短くするために、使用するソリューション putFragmentするために、後で参照を取得できるようにするためするはそれほどクレイジーではなく、とにかくフラグメントを使用する通常の方法(上記)とそれほど異なりません。個人的にではなく、アダプタによってフラグメントが追加されるため、他の方法で参照を取得することは困難です。offscreenPageLimit存在することに依存しているので、が常に必要なフラグメントをロードできる十分な高さであることを確認してください。これは、ViewPagerの遅延読み込み機能をバイパスしますが、アプリケーションに必要な機能のようです。

別のアプローチはオーバーライドすることです FragmentPageAdapter.instantiateItem(View, int)、super呼び出しから返されたフラグメントへの参照して保存してから返すことです(フラグメントがすでに存在する場合は、フラグメントを見つけるロジックがあります)。

全体像については、FragmentPagerAdapter(short)とViewPager(long)のソースの一部をご覧ください


7
最後の部分を追加しました。フラグメントのキャッシュがあり、プットを内のキャッシュロジックに移動しましたFragmentPageAdapter.instantiateItem(View, int)。最後に、ローテーション/構成の変更でのみ表示され、私を狂わせていた長期にわたるバグを修正しました...
Carlos Sobrinho

2
これにより、回転の変更で発生していた問題が修正されました。探しているだけでは見えないのかもしれませんが、これは文書化されていますか?つまり、タグを使用して以前の状態から回復しますか?当然の答えかもしれませんが、私はAndroid開発にかなり慣れていません。

1
@ Industrial-antidepressantこれは、フラグメントが追加されるコンテナー(例:FrameLayout)のIDです。
antonyt 2013年

1
ところで、私にとっては、とFragmentPageAdapter.instantiateItem(ViewGroup, int)いうよりはFragmentPageAdapter.instantiateItem(View, int)
表示名

1
ところで、フラグメントが画面からスワイプされたときに呼び出されるフラグメントのライフサイクルメソッド(ある場合)を知っていますか?これでonDetach()、または他の何か?
イジェシャー2014

36

antonytすばらしい回答と、後でFragmentPageAdapter.instantiateItem(View, int)作成した参照を保存するためにオーバーライドをオーバーライドすることに言及したソリューションを提供して、Fragments後でそれらを処理できるようにしたいと考えています。これはでも動作するはずFragmentStatePagerAdapterです。詳細については注記を参照してください。


これは、の内部セットに依存しないFragments、によって返されたへの参照を取得する方法の簡単な例です。重要なのは、の代わりに参照を上書きして保存することです。FragmentPagerAdaptertagsFragmentsinstantiateItem()getItem()

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

またはあなたが作業する場合tags、クラスのメンバ変数の代わりに/への言及FragmentsあなたもつかむことができるtagsことでセットをFragmentPagerAdapter同じように:注:これはには適用されませんFragmentStatePagerAdapter、それが設定されていませんので、tagsそのを作成するときFragments

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

このメソッドは内部tagセットの模倣に依存せず、FragmentPagerAdapterそれらを取得するために適切なAPIを使用することに注意してください。このようにしてtag、将来のバージョンの変更がSupportLibrary安全です。


忘れてはいけないこと、あなたのデザインに応じてActivityFragmentsあなたが仕事にしようとしているのかもしれないまたはあなたが持っているそうで、そのためにアカウントに、まだ存在しない場合がありますnull、あなたの参照を使用する前にチェックを。

また、代わりにを使用している場合はFragmentStatePagerAdapter、ハード参照をFragments多数保持している可能性があり、ハード参照が不必要にそれらをメモリに保持するため、ハード参照を保持する必要はありません。代わりに、Fragment参照WeakReferenceを標準のものではなく変数に保存します。このような:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}

メモリ不足の場合、androidはFragmentManagerに未使用のフラグメントを破棄するよう要求することがありますよね?私が正しければ、2番目のバージョンは機能し、最初のバージョンは失敗する可能性があります。(現在のAndroidバージョンがこれを行うかどうかはわかりませんが、それを期待する必要があるようです。)
Moritz両方

1
FragmetPagerAdapter画面の回転ごとにonCreate of Activityを作成するのはどうですか?すでに追加されたフラグメントの再利用をバイパスする可能性があるため、これは間違っていますかFragmentPagerAdapter
Bahman

オーバーライドするのinstantiateItem()がよい方法です。これにより、画面の回転を処理し、アクティビティとアダプタが再開されたら、既存のFragmentインスタンスを取得できました。念のため、コードにコメントを残しましたgetItem()。ローテーション後は呼び出されません。このメソッドのみinstantiateItem()が呼び出されます。instantiateItem()新しいインスタンスをインスタンス化する代わりに、(必要に応じて)実際にフラグメントを再アタッチするためのスーパー実装!
HelloImKevo

Fragment createdFragment = (Fragment) super.instantiateItem..最初の解決策でnullポインターを取得しています 。
AlexS

この問題のさまざまな重複/バリアント反復をすべて調査した後、これが最良の解決策でした。(より関連性の高いタイトルで別のQから相互参照されているので、ありがとうございました!)
MandisaW

18

私はあなたの質問に対して別の比較的簡単な解決策を見つけました。

FragmentPagerAdapterのソースコードからわかるように、次のコードを使用して生成されたタグFragmentPagerAdapterFragmentManager下に、store によって管理されるフラグメントが格納されています。

String tag="android:switcher:" + viewId + ":" + index;

viewIdcontainer.getId()containerあなたのあるViewPagerインスタンス。index断片の位置です。したがって、オブジェクトIDをに保存できますoutState

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

このフラグメントと通信したい場合はFragmentManager、次のようにから取得できます。

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")

21
ええと、これは決して永遠に同じであるとは限らない内部タグの命名規則に返信するのに適した方法だとは思いません。
ドリ

1
内部tagに依存しないソリューションを探している人のために、私の答えを試すことを検討してください
トニーチャン

16

答えの検索の多くが私をこのスレッドに導き続けたので、おそらく少し異なるケースの代替ソリューションを提供したいと思います。

私の場合 -ページを動的に作成/追加し、それらをViewPagerにスライドしていますが、回転(onConfigurationChange)すると、当然OnCreateが再度呼び出されるため、新しいページになります。しかし、ローテーションの前に作成されたすべてのページへの参照を保持したいと思います。

問題 -作成するフラグメントごとに一意の識別子がないため、参照する唯一の方法は、ローテーション/構成の変更後に復元されるように参照を配列に格納することでした。

回避策 -重要なコンセプトは、アクティビティ(フラグメントを表示)が既存のフラグメントへの参照の配列も管理することでした。このアクティビティはonSaveInstanceStateでバンドルを利用できるためです。

public class MainActivity extends FragmentActivity

このアクティビティでは、開いているページを追跡するプライベートメンバーを宣言します

private List<Fragment> retainedPages = new ArrayList<Fragment>();

これは、onCreateInstanceStateが呼び出され、onCreateで復元されるたびに更新されます。

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

...いったん保存すると、取得できます...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

これらはメインアクティビティに必要な変更であり、これを機能させるにはFragmentPagerAdapter内のメンバーとメソッドが必要でした。

public class ViewPagerAdapter extends FragmentPagerAdapter

同一の構成(上記のMainActivityに示されている)

private List<Fragment> _pages = new ArrayList<Fragment>();

この同期(上記のonSaveInstanceStateで使用)は、特にメソッドによってサポートされています

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

そして最後に、フラグメントクラスで

public class CustomFragment extends Fragment

これらすべてが機能するために、2つの変更がありました。

public class CustomFragment extends Fragment implements Serializable

これをonCreateに追加して、フラグメントが破棄されないようにします

setRetainInstance(true);

私はまだフラグメントとAndroidのライフサイクルに頭を悩ませています。そのため、この方法には冗長性/非効率性がある可能性があることに注意してください。しかし、それは私にとってはうまくいき、私と同じようなケースを持つ他の人にも役立つことを願っています。


2
+1 for setRetainInstance(true)-私が探していた黒魔術!
語形変化

FragmentStatePagerAdapter(v13)を使用すると、さらに簡単になりました。状態の復元と解放をどのように処理するか。
肉屋14

明確な説明と非常に素晴らしい解決策。ありがとうございました!
ブルーノビエリ

8

私のソリューションは非常に失礼ですが機能します。保持されたデータから動的に作成されたフラグメントであるため、PageAdapter呼び出す前にすべてのフラグメントをから削除super.onSaveInstanceState()し、アクティビティの作成時にそれらを再作成します。

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

でそれらを削除することはできません。削除しonDestroy()ないと、この例外が発生します。

java.lang.IllegalStateException: 後にこのアクションを実行できません onSaveInstanceState

ここにページアダプタのコードがあります:

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

onCreate()フラグメントが作成された後、現在のページを保存してで復元するだけです。

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  

理由はわかりませんが、notifyDataSetChanged();です。アプリがクラッシュする
Malachiasz 2013年

4

それは何BasePagerAdapterですか?標準ページャーアダプターの1つを使用する必要があります- FragmentPagerAdapterまたはのいずれかFragmentStatePagerAdapterを使用する必要がありViewPagerます。再び必要。

使用ViewPagerするためのサンプルコードはここにあります

FragmentManagerフレームワークでは、ページャーが作成したアクティブなフラグメントの状態の保存と復元をフレームワークが処理するため、アクティビティインスタンス全体のビューページャーでのフラグメントの管理は少し複雑です。これが実際に意味することは、初期化時にアダプターが、復元されたフラグメントがあればそれと再接続することを確認する必要があるということです。あなたはコードを見ることができますFragmentPagerAdapterFragmentStatePagerAdapterを見るか、これがどのように行わかを見る。


1
BasePagerAdapterコードは私の質問で利用できます。ご覧のとおり、それは単にTitleProviderを実装する目的でFragmentPagerAdapterを拡張しています。したがって、すべてがすでにandroid devの推奨アプローチで機能しています。
Oleksii Malovanyi

「FragmentStatePagerAdapter」を知りませんでした。あなたは文字通り私を救いました。ありがとう。
クノッソ

2

FragmentStatePagerAdapterがフラグメントの状態を適切に復元できないという問題がある場合...つまり、新しいフラグメントは、状態から復元するのではなく、FragmentStatePagerAdapterによって作成されています...

電話をかけるViewPager.setOffscreenPageLimit()前に必ず電話してくださいViewPager.setAdapter(fragmentStatePagerAdapter)

ViewPager.setOffscreenPageLimit()... を呼び出すと、ViewPagerはすぐにそのアダプタを調べて、そのフラグメントの取得を試みます。これは、ViewPagerがフラグメントをsavedInstanceStateから復元する前に発生する可能性があります(したがって、新しいフラグメントであるため、SavedInstanceStateから再初期化できません)。


0

私はこのシンプルでエレガントなソリューションを思いつきました。これは、アクティビティがフラグメントの作成を担当し、アダプタがそれらを提供することを前提としています。

これはアダプタのコードです(mFragmentsアクティビティによって維持されるフラグメントのリストであるという事実を除いて、ここでは奇妙なことは何もありません)。

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

このスレッドの問題全体が「古い」フラグメントの参照を取得しているため、アクティビティのonCreateでこのコードを使用します。

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

もちろん、必要に応じてこのコードをさらに微調整できます。たとえば、フラグメントが特定のクラスのインスタンスであることを確認できます。


0

方向変更後にフラグメントを取得するには、.getTag()を使用する必要があります。

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

もう少し処理するために、任意の位置でviewPagerIdおよびFragmentClassによってフラグメントを取得するために、PageAdapterの独自のArrayListを作成しました。

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

@Override
public CharSequence getPageTitle(int position) {
    return this.fragmentPages.get(position).getPageTitle();
}

@Override
public int getCount() {
    return this.fragmentPages.size();
}


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

したがって、フラグメントを使用してMyPageArrayListを作成するだけです。

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

そしてそれらをviewPagerに追加します:

    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

この後、クラスを使用して正しいフラグメントを方向変更後に取得できます。

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));

-30

追加:

   @SuppressLint("ValidFragment")

あなたのクラスの前に。

それが機能しない場合は、次のようなことをしてください:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.