Android Fragmentバックスタックの問題


121

私は、androidフラグメントのバックスタックが機能しているように見える方法に大きな問題を抱えており、提供されたヘルプに最も感謝しています。

あなたが3つのフラグメントを持っていると想像してください

[1] [2] [3]

ユーザーがナビゲートできるようにし[1] > [2] > [3]ながら、戻る途中([戻る]ボタンを押す)にしたい[3] > [1]

私が想像したように、これは、XMLで定義されたフラグメントホルダーにaddToBackStack(..)フラグメントをもたらすトランザクションを作成するときに呼び出しを行わないことによって達成[2]されます。

この現実は[2]、ユーザーが戻るボタンを押したときに再び表示され[3]ないようaddToBackStackにするには、フラグメントを表示するトランザクションで呼び出してはならないように見えます[3]。これは完全に直観に反しているようです(おそらくiOSの世界から来ているでしょう)。

とにかく、この方法でそれを行うと、[1] > [2]私が戻って押したときに[1]、期待どおりに戻ります。

私が行っ[1] > [2] > [3]てから戻ると、[1](予想どおり)に戻ります。[2]からジャンプしようとすると、奇妙な動作が発生し[1]ます。まず、[3]表示される前に簡単に表示さ[2]れます。この時点で「戻る」を押す[3]と表示され、もう一度「戻る」を押すとアプリが終了します。

誰かが私がここで起こっていることを理解するのを手伝ってくれる?


そして、これが私の主な活動のレイアウトxmlファイルです。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >

<fragment
        android:id="@+id/headerFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        class="com.fragment_test.FragmentControls" >
    <!-- Preview: layout=@layout/details -->
</fragment>
<FrameLayout
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"

        />



更新 これは、nav heirarchyによるビルドに使用しているコードです

    Fragment frag;
    FragmentTransaction transaction;


    //Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack
    frag = new Fragment1();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();

    //Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack
    frag = new Fragment2();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();


    //Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2] 
    frag = new Fragment3();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();


     //END OF SETUP CODE-------------------------
    //NOW:
    //Press back once and then issue the following code:
    frag = new Fragment2();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();

    //Now press back again and you end up at fragment [3] not [1]

どうもありがとう


私は断片CからのフラグメントAにbackpressときにはフラグメントが、重複している
して、Priyanka

同じ問題がありますが、どうすれば修正できますか?
Priyanka

私の回答を参照してください。それはあなたを助けるかもしれません< stackoverflow.com/questions/14971780/… >
MD Khali

回答:


203

説明:ここで何が起こっているのですか?

それ.replace().remove().add()ドキュメントで知っていることと等しいことを心に留めておくと:

コンテナに追加された既存のフラグメントを置き換えます。これは、基本的に、remove(Fragment)現在追加されているすべてのフラグメントを呼び出したcontainerViewId場合add(int, Fragment, String)と同じです。

その後、何が起こっているのかは次のとおりです(フラグをより明確にするために、フラグに番号を追加しています)。

// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1)  // frag1 on view

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null)  // frag2 on view

// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3)  // frag3 on view

(ここですべての誤解を招くようなことが起こり始めます)

それを覚えておいてください.addToBackStack()保存しているだけのトランザクションではないフラグメント自体を!これでfrag3レイアウトが整いました:

< press back button >
// System pops the back stack and find the following saved back entry to be reversed:
// [Transaction.remove(frag1).add(frag2)]
// so the system makes that transaction backward!!!
// tries to remove frag2 (is not there, so it ignores) and re-add(frag1)
// make notice that system doesn't realise that there's a frag3 and does nothing with it
// so it still there attached to view
Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING)

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag3).add(frag2).addToBackStack(null)  //frag2 on view

< press back button >
// system makes saved transaction backward
Transaction.remove(frag2).add(frag3) //frag3 on view

< press back button >
// no more entries in BackStack
< app exits >

可能な解決策

FragmentManager.BackStackChangedListenerバックスタックの変更を監視し、onBackStackChanged()メソッドにロジックを適用するための実装を検討してください。


良い説明@arvis。ただし、DexterMoonのようなハッキーな方法や、遷移アニメーションの再生中にフラグメントを表示するpopBackStackを使用するNemanjaのようなハッキーな方法に頼らずにこの動作を防ぐにはどうすればよいでしょうか。
momo 2013

FragmentManager.BackStackChangedListenerバックスタックへの変更を監視するために実装する@momo 。onBackStackChanged()methodeを使用してすべてのトランザクションを監視し、必要に応じて行動します。BackStackのトランザクション数をトレースします。名前(FragmentTransaction addToBackStack (String name))などで特定のトランザクションをチェックします
Arvis '27 / 09/27

回答ありがとうございます。今日私は実際にそれを試してみて、リスナーを登録し、onBackstackChangeからフラグメントを削除しました。飛び出るトランジションが再生されている間、フラグメントは白い空の領域になります。ポップがアニメーションの開始時に起動され、終了時に起動されないと思います...
momo

4
バックスタックの使用は避けてください!それは全体的な効率を実際には助けません!単純なreplace()を使用するか、ナビゲートするたびに削除/追加してください!
stack_ved 2014

@Arvisは、同じ問題が発生するのを助けることができます....スタックカウント0ですが、それでもフラグメントは表示されますか?
Erum

33

正しい!!!多くの髪を引っ張った後、私は最終的にこれを適切に機能させる方法を考え出しました。

戻る[3]を押してもフラグメント[3]がビューから削除されないので、手動で行う必要があります。

まず、replace()を使用しないでください。代わりに、removeとaddを個別に使用してください。replace()が正しく機能していないようです。

これの次の部分は、onKeyDownメソッドをオーバーライドして、戻るボタンが押されるたびに現在のフラグメントを削除することです。

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    if (keyCode == KeyEvent.KEYCODE_BACK)
    {
        if (getSupportFragmentManager().getBackStackEntryCount() == 0)
        {
            this.finish();
            return false;
        }
        else
        {
            getSupportFragmentManager().popBackStack();
            removeCurrentFragment();

            return false;
        }



    }

    return super.onKeyDown(keyCode, event);
}


public void removeCurrentFragment()
{
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

    Fragment currentFrag =  getSupportFragmentManager().findFragmentById(R.id.detailFragment);


    String fragName = "NONE";

    if (currentFrag!=null)
        fragName = currentFrag.getClass().getSimpleName();


    if (currentFrag != null)
        transaction.remove(currentFrag);

    transaction.commit();

}

お役に立てれば!


これは機能しますが、フラグメントがリロードされるため、私のプロジェクトでは使用できません!助言がありますか?
TharakaNirmana 2014

しかし、私がこれをしているなら。私は断片C.のためにCからAに戻って来ていたときに、空白の画面を取得しています
ニガムPatro

@Arvisの回答が問題に光を当てるべきだと思います
Chris Birch

16

まず最初に、@ Arvisの目を引く説明に感謝します。

この問題については、ここで認められた回答とは異なる解決策を好む。私は絶対に必要以上にオーバーライド動作を無効にするのが好きではありません。デフォルトのバックスタックポップアップなしでフラグメントを自分で追加および削除しようとすると、戻るボタンが押されたときに自分がフラグメント地獄にいるのがわかりました:)もし。削除するときにf2をf1に追加します。f1はonResume、onStartなどのコールバックメソッドを呼び出さないため、非常に残念です。

とにかくこれは私がそれをする方法です:

現在表示されているのはフラグメントf1のみです。

f1-> f2

Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();

ここでは普通のことは何もありません。フラグメントf2よりも、このコードを使用すると、f3をフラグメント化できます。

f2-> f3

Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

これが機能するかどうかはドキュメントを読んで確信が持てませんが、このポップアップトランザクションメソッドは非同期であると言われています。しかし、私のデバイスでは、問題なく機能していると言えます。

上記の代替案は次のとおりです。

final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

ここでは実際にf1に戻ってf3に移行するまでの簡単な説明があるため、わずかな不具合があります。

これは実際にあなたがしなければならないすべてです、バックスタックの動作をオーバーライドする必要はありません...


6
しかし、グリッチ、それが問題です
Zyoo '16

これは、BackStackの変更をリッスンするよりも "hack-ey"が少ないようです。ありがとう。
2014

13

私はそれが古い質問であることを知っていますが、同じ問題が発生し、次のように修正しました:

最初に、Fragment1をBackStackに名前(「Frag1」など)で追加します。

frag = new Fragment1();

transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();

そして、Fragment1に戻りたいときはいつでも(その上に10個のフラグメントを追加した後でも)、名前でpopBackStackImmediateを呼び出すだけです。

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

それが誰かを助けることを願っています:)


1
すばらしい答えです。私はgetSupportFragmentManager()。popBackStackImmediate( "Frag1"、FragmentManager.POP_BACK_STACK_INCLUSIVE);を使用する必要がありました。私のユースケースで機能させるには
eliasbagley

1
このコードをどこに置く必要がありますか???? getSupportFragmentManager()。popBackStackImmediate( "Frag1"、0); MainActivtyまたはonBackPressオン
パベル

動いていない。。:(これは私のコードは、フラグメントAを開く私の場合にはフラグメントはOverlapingであるが、Frgment Aは、フラグメントBにオーバーラップである。
て、Priyanka

FragmentManager fragmentManager = getActivity()。getSupportFragmentManager(); fragmentManager.popBackStack(FragmentA.class.getName()、FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); FragmentE fragmentE = new FragmentE(); fragmentTransaction.replace(R.id.fragment_content、fragmentE、fragmentE.getClass()。getName()); fragmentTransaction.commit();
Priyanka

5

@Arvisの返信後、私はさらに深く掘り下げることを決め、ここでこれに関する技術記事を書きました:http ://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- due-to-backstack-nightmare-in-android /

周りの怠惰な開発者のために。私の解決策は、トランザクションを常にバックスタックに追加しFragmentManager.popBackStackImmediate()、必要に応じて(自動的に)追加を実行することです。

コードはほんの数行のコードであり、私の例では、ユーザーがバックスタックの奥に行かなかった場合(「CからDにナビゲートする」など)は「B」にジャンプせずにCからAにスキップしたかったのです。

したがって、添付されたコードは次のように機能しますA-> B-> C(戻る)-> A&A-> B-> C-> D(戻る)-> C(戻る)-> B(戻る)-> A

どこ

fm.beginTransaction().replace(R.id.content, new CFragment()).commit()

質問のように「B」から「C」に発行されました

さて、ここにコードがあります:)

public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) {
  final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1;

  fragmentManager.beginTransaction()
      .replace(R.id.content, fragment, tag)
      .addToBackStack(tag)
      .commit();

  fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
      int nowCount = fragmentManager.getBackStackEntryCount();
      if (newBackStackLength != nowCount) {
        // we don't really care if going back or forward. we already performed the logic here.
        fragmentManager.removeOnBackStackChangedListener(this);

        if ( newBackStackLength > nowCount ) { // user pressed back
          fragmentManager.popBackStackImmediate();
        }
      }
    }
  });
}

1
この行でクラッシュしますfragmentManager.popBackStackImmediate();エラー:java.lang.IllegalStateException:FragmentManagerはcom.example.myapplication.FragmentA $ 2.onBackStackChanged(FragmentA.java:43)でトランザクションを既に実行しています
Priyanka

1

addToBackStack()とpopBackStack()で苦労している場合は、単に

FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`

OnBackPressed()のアクティビティでタグで分岐を見つけてから、

Fragment home = getSupportFragmentManager().findFragmentByTag("Home");

if (home instanceof HomeFragment && home.isVisible()) {
    // do you stuff
}

詳細情報https://github.com/DattaHujare/NavigationDrawer フラグメントの処理にaddToBackStack()を使用することはありません。


0

[3]もバックスタックにあるという話を読んだとき、私は思います。これが、点滅する理由を説明しています。

解決策は、スタックに[3]を設定しないことです。


こんにちはjdekeiご入力いただきありがとうございます。問題は、[3]をバックスタックに追加している場所が見えないことです。ボタンを使用して実行していたナビゲーションを(プログラムで)正確に示す別のコードを追加しました。
Chris Birch、

それも私には役立ちますが、私はあなたのコードからremoveCurFragmentと少し別のフラグメントチェッカーのみを使用しています。Replaceメソッドは、私にとってはうまく機能しますs old method issue but now it。ありがとう
Viktor V.

0

同じ[M1.F0]-> [M1.F1]-> [M1.F2]に3つの連続するフラグメントがあり、Activityその後に新しいActivity[M2]が呼び出されるという同様の問題がありました。ユーザーが[M2]でボタンを押した場合、[M1、F2]ではなく[M1、F1]に戻りたいと考えていました。

これを達成するために、[M1、F2]を削除し、[M1、F1]でshowを呼び出し、トランザクションをコミットしてから、hideで呼び出して[M1、F2]を追加します。これにより、残されていたはずの余分なバックプレスが取り除かれました。

// Remove [M1.F2] to avoid having an extra entry on back press when returning from M2
final FragmentTransaction ftA = fm.beginTransaction();
ftA.remove(M1F2Fragment);
ftA.show(M1F1Fragment);
ftA.commit();
final FragmentTransaction ftB = fm.beginTransaction();
ftB.hide(M1F2Fragment);
ftB.commit();

こんにちはこのコードを実行した後:Backキーを押したときにFragment2の値が表示されません。私のコード:

FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.frame, f1);
ft.remove(f1);

ft.add(R.id.frame, f2);
ft.addToBackStack(null);

ft.remove(f2);
ft.add(R.id.frame, f3);

ft.commit();

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event){

        if(keyCode == KeyEvent.KEYCODE_BACK){
            Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);
            FragmentTransaction transaction = getFragmentManager().beginTransaction();

            if(currentFrag != null){
                String name = currentFrag.getClass().getName();
            }
            if(getFragmentManager().getBackStackEntryCount() == 0){
            }
            else{
                getFragmentManager().popBackStack();
                removeCurrentFragment();
            }
       }
    return super.onKeyDown(keyCode, event);
   }

public void removeCurrentFragment()
    {
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);

        if(currentFrag != null){
            transaction.remove(currentFrag);
        }
        transaction.commit();
    }

0

executePendingTransactions()、動作しcommitNow()ません(

androidx(jetpack)で作業しました。

private final FragmentManager fragmentManager = getSupportFragmentManager();

public void removeFragment(FragmentTag tag) {
    Fragment fragmentRemove = fragmentManager.findFragmentByTag(tag.toString());
    if (fragmentRemove != null) {
        fragmentManager.beginTransaction()
                .remove(fragmentRemove)
                .commit();

        // fix by @Ogbe
        fragmentManager.popBackStackImmediate(tag.toString(), 
            FragmentManager.POP_BACK_STACK_INCLUSIVE);
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.