Androidフラグメント-別のフラグメントがその上にプッシュされたときにフラグメントのビューの状態を保存する方法


137

Androidでは、フラグメント(たとえばFragA)がバックスタックに追加され、別のフラグメント(たとえばFragB)が一番上に来ます。さて、打ち返すFragAとトップに来てonCreateView()呼ばれます。今、私はFragAそのFragB上に押される前に特定の状態にありました。

私の質問は、どうすればFragA以前の状態に復元できますか?状態を保存する方法はありますか(Bundleのように)、その場合はどのメソッドをオーバーライドする必要がありますか?

回答:


98

ではフラグメントガイド FragmentList例を探すことが出来ます:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

これは後でこのように使用できます:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

私はFragmentsの初心者ですが、問題の解決策のようです;)OnActivityCreatedは、フラグメントがバックスタックから戻った後に呼び出されます。


18
これを機能させることができませんでしたsavedInstanceStateは常にnullでした。XMLレイアウトを介してフラグメントを追加しています。mCurCheckPositionをstaticに変更する必要があり、機能しますが、ハック感があります。
scottyab

57
それはonSaveInstanceStateを呼び出しません-なぜそれをするのですか?したがって、このアプローチは機能しません。
午前

10
同じアクティビティの別のフラグメントから戻るときにフラグメントの状態を保持したい場合、このアプローチは本当に機能しますか?onSaveInstanceState()は、アクティビティのonPause / onStopイベントでのみ呼び出されます。ドキュメントによれば、「アクティビティと同様に、アクティビティのプロセスが強制終了され、アクティビティが再作成されたときにフラグメントの状態を復元する必要がある場合は、バンドルを使用してフラグメントの状態を保持できます。フラグメントのonSaveInstanceState()コールバックを呼び出し、onCreate()、onCreateView()、またはonActivityCreated()のいずれかで復元します。
Paramvir Singh 2013

26
記録としては、このアプローチは間違っており、賛成票の近くにはどこもないはずです。onSaveInstanceState対応するアクティビティもシャットダウンしているときにのみ呼び出されます。
Martin Konecny 2014

16
onSaveInstanceState()は、構成の変更が発生し、アクティビティが破棄されたときにオンリーで呼び出されます。この答えは間違っています
Tadas Valaitis

83

フラグメントonSaveInstanceState(Bundle outState)は、フラグメントのアクティビティがそれ自体およびアタッチされたフラグメントで呼び出すまで呼び出されません。したがって、このメソッドは、何か(通常は回転)によってアクティビティが強制SaveInstanceStateされ、後で復元されるまで呼び出されません。ただし、アクティビティが1つだけあり、その中に大量のフラグメントセットがある場合(を頻繁に使用replace)、アプリケーションが1つの方向でのみ実行onSaveInstanceState(Bundle outState)される場合、アクティビティが長時間呼び出されない場合があります。

考えられる回避策は3つあります。

最初:

フラグメントの引数を使用して重要なデータを保持します。

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

2番目ですが、それほど面倒ではない方法-変数をシングルトンで保持します

3番目- replace()断片化しないでくださいadd()/ show()/ / hide()それらの代わりに。


3
Fragment.onSaveInstanceState()呼び出されたことがないときの最良のソリューション。リストビュー内のアイテムまたはそれらのIDのみを含む独自のデータを引数に保存するだけです(他の集中データマネージャーがある場合)。リストビューの位置を保存する必要はありません。保存され、自動的に復元されます。
John Pang

私のアプリであなたの例を使用しようとしましたが、これString persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);は常にnullです。どうしたの?
フラゴン2015年

フラグメントの使用はgetArguments()、ViewPager内のネストされたフラグメントを含め、確実に進むべき道です。私は1つのアクティビティを使用し、多くのフラグメントをイン/アウトでスワップします。これは完全に機能します。次に、提案されたソリューションを確認するための簡単なテストを示します。1)フラグメントAからフラグメントBに移動します。2)デバイスの向きを2回変更します。3)デバイスの戻るボタンを押します。
アンディH.

最初の方法を試しましたが、うまくいきませんでした。getArguments()は常にnullを返します。また、フラグメントが置き換えられ、onCreate()で新しいバンドルを設定して古いバンドルが失われるため、これも意味があります。何が欠けているのですか?
Zvi 2016年

@Zvi、私はこのコードを1.5年前に書き、すべての詳細を覚えていませんが、覚えているように、置換はフラグメントを再作成しません。フラグメントは、コードから新しいインスタンスを作成した場合にのみ再作成されます。この場合、明らかに、コンストラクターが呼び出され、setArguments(new Bundle());古いバンドルを上書きします。したがって、フラグメントを1回だけ作成し、毎回新しいフラグメントを作成する代わりに、このインスタンスを使用してください。
Fyodor Volchyok 2016年

20

ViewPagerを使用してFragmentsを操作する場合、それは非常に簡単です。このメソッドを呼び出すだけですsetOffscreenPageLimit()

ドキュメントに合わせる:

保持する必要があるページの数を、アイドル状態のビュー階層の現在のページのいずれかの側に設定します。この制限を超えるページは、必要に応じてアダプターから再作成されます。

ここで同様の問題


5
これは違います。setOffScreenPageLimitはキャッシュのように機能し(特定の瞬間にViewPagerが処理しなければならないページの数を意味します)、フラグメントの状態を保存するためには使用されません。
Renaud Mathieu

私の場合、setOffscreenPageLimit()で動作しました-フラグメントは破棄されましたが、ビューステートは保存および復元されました。
Davincho、2014

ありがとう、私も助けてくれました。
Elijah

数年後、これはまだ非常に関連しています。それは実際には質問に答えませんが、問題を解決します
Supreme Dolphin

19

ビューを一度だけ膨らませてください。

例は次のとおりです。

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}


また、既存のフラグメントを配列などに保持する必要があります
Amir

フラグメントのrootViewへの参照を維持することは悪い習慣であると聞いた-リークが発生する可能性がある[引用が必要]?
giraffe.guru 2017

1
@ giraffe.guru Fragmentがそのルートビューを参照すると、それは行われません。一部のGCルート要素による参照は、グローバル静的プロパティと同様に、非UIスレッド変数になります。FragmentインスタンスはGC-ルートではないので、ゴミを収集することができます。ルートビューも同様です。
Lym Zoy 2017

あなたは私の日と同じです。
Vijendra patidar

甘くてシンプル。
Rupam Das

8

私はこれとよく似た問題に取り組みました。以前のフラグメントに頻繁に戻ることがわかっていたので、フラグメント.isAdded()が本当かどうかを確認しました。本当の場合は、を実行するのではなく、transaction.replace()を実行しましたtransaction.show()。これにより、フラグメントがすでにスタック上にある場合にフラグメントが再作成されるのを防ぎます。

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

覚えておくべきもう1つのことは、これによりフラグメント自体の自然な順序は維持されますが、方向(config)の変更時に破棄および再作成されるアクティビティ自体を処理する必要がある場合があることです。ノードのAndroidManifest.xmlでこれを回避するには:

android:configChanges="orientation|screenSize"

Android 3.0以降でscreenSizeは、明らかに必要です。

幸運を


transaction.addToBackStack(button_id + "stack_item"); //この行は何をします。ここでbutton_idとは何ですか?
raghu_3 2014年

button_idは単なる変数です。addToBackStackに渡される文字列引数は、バックスタック状態のオプションの名前にすぎません。単一のバックスタックのみを管理している場合は、nullに設定できます。
rmir​​abelle 2014年

、私はあなたがIT-に表情をしてください、フラグメントとこれと同様の問題をすることができたのですstackoverflow.com/questions/22468977/...
raghu_3

1
android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsマニフェストを追加しないでください。代わりに、たとえば、ここから、speakerdeck.com
MarcinKoziński

そして、保存状態がめちゃくちゃ扱いにくいのがわかり、提示されたソリューションが特定の状況で問題を最もきれいに解決することを理解したら、同意しない人からの反対投票を気にせずに先に進んでそれを使用してください;-)
rmir​​abelle

5

私が見つけた最良の解決策は以下の通りです:

onSavedInstanceState():アクティビティがシャットダウンされるときに常にフラグメント内で呼び出されます(アクティビティを別のアクティビティに移動するか、構成を変更します)。したがって、同じアクティビティで複数のフラグメントを呼び出す場合は、次のアプローチを使用する必要があります。

フラグメントのOnDestroyView()を使用して、オブジェクト全体をそのメソッド内に保存します。次にOnActivityCreated():オブジェクトがnullかどうかを確認します(このメソッドが毎回呼び出されるため)。ここでオブジェクトの状態を復元します。

常に動作します!


4

あなたがこのようにAndroidマニフェストで指定されたフラグメントアクティビティの設定変更を処理している場合

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

その後、onSaveInstanceStateフラグメントのは呼び出されず、savedInstanceStateオブジェクトは常にnullになります。


1

私はonSaveInstanceState良い解決策だとは思わない。それは破壊されていた活動に使用するだけです。

android 3.0からFragmenはFragmentManagerによってマネージャーになりました。条件は次のとおりです。1つのアクティビティマッピングのマニーフラグメント。フラグメントがbackStackに追加されると(置換ではなく、再作成されます)、ビューが復元されます。最後に戻ると、以前と同じように表示されます。

ですから、fragmentMangerとトランザクションはそれを処理するのに十分良いと思います。


0

リストビューを含むフラグメントにハイブリッドアプローチを使用しました。現在のフラグメントを置き換えるのではなく、新しいフラグメントを追加して現在のフラグメントを非表示にするため、パフォーマンスが向上しているようです。私のフラグメントをホストするアクティビティには次のメソッドがあります:

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

リストアイテムがクリック/タップされるたびに(リストビューを含む)このメソッドをフラグメントで使用します(したがって、詳細フラグメントを起動/表示する必要があります)。

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags()新しいフラグメントを追加すると、さまざまなフラグメントのタグとして使用する文字列の配列を返します(のtransaction.addメソッドを参照)addFragment上記方法を)。

リストビューを含むフラグメントでは、これをonPause()メソッドで行います。

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

次に、フラグメントのonCreateView(実際にはonCreateViewで呼び出されるメソッド)で、状態を復元します。

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}

0

最後に、これらの複雑なソリューションの多くを試した後、Fragment(EditTextのコンテンツ)に単一の値を保存/復元するだけでよいので、最も洗練されたソリューションではないかもしれませんが、SharedPreferenceを作成して状態を保存します私のために働いた


0

アクティビティのさまざまなフラグメントのフィールドの値を保持する簡単な方法

フラグメントのインスタンスを作成し、置換と削除の代わりに追加します

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

次に、フラグメントを再度追加および削除する代わりに、フラグメントを表示および非表示にします

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;


-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}

5
単にコードを投稿するのではなく、回答に関するいくつかの情報を含めることを検討してください。私たちは「フィックス」を提供するだけでなく、人々が学ぶのを助けるように努めます。元のコードの何が間違っていたのか、何が違ったのか、なぜ変更が機能したのかを説明する必要があります。
Andrew Barber
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.