存在する場合にBackStackからフラグメントを再開する方法


151

フラグメントの使い方を学んでいます。Fragmentクラスの先頭で初期化されているインスタンスが3つあります。このようなアクティビティにフラグメントを追加しています:

宣言と初期化:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

交換/追加:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

これらのスニペットは正しく機能しています。すべてのフラグメントはアクティビティに添付され、問題なくバックスタックに保存されます。

したがって、を起動するACBスタックは次のようになります。

| |
|B|
|C|
|A|
___

そして「戻る」ボタンを押すと、B破壊されCて再開されます。

しかし、フラグメントをもう一度起動Aすると、バックスタックから再開するのではなく、フラグメントがバックスタックの一番上に追加されます

| |
|A|
|C|
|A|
___

しかしA、その上にあるすべてのフラグメント(ある場合)を再開して破棄したいと思います。実際、私はデフォルトのバックスタック動作が好きです。

どうすればこれを達成できますか?

予想:(A再開し、上部のフラグメントを破棄する必要があります)

| |
| |
| |
|A|
___

編集:(A--Cが推奨)

これは私の試行コードです:

private void selectItem(int position) {
        Fragment problemSearch = null, problemStatistics = null;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        String backStateName = null;
        Fragment fragmentName = null;
        boolean fragmentPopped = false;
        switch (position) {
        case 0:
            fragmentName = profile;
            break;
        case 1:
            fragmentName = submissionStatistics;
            break;
        case 2:
            fragmentName = solvedProblemLevel;
            break;
        case 3:
            fragmentName = latestSubmissions;
            break;
        case 4:
            fragmentName = CPExercise;
            break;
        case 5:
            Bundle bundle = new Bundle();
            bundle.putInt("problem_no", problemNo);
            problemSearch = new ProblemWebView();
            problemSearch.setArguments(bundle);
            fragmentName = problemSearch;
            break;
        case 6:
            fragmentName = rankList;
            break;
        case 7:
            fragmentName = liveSubmissions;
            break;
        case 8:
            Bundle bundles = new Bundle();
            bundles.putInt("problem_no", problemNo);
            problemStatistics = new ProblemStatistics();
            problemStatistics.setArguments(bundles);
            fragmentName = problemStatistics;
        default:
            break;
        }
        backStateName = fragmentName.getClass().getName();
        fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
        if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();

        // I am using drawer layout
        mDrawerList.setItemChecked(position, true);
        setTitle(title[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

問題は、起動してAからB「戻る」を押すBと、削除されてA再開されることです。もう一度「戻る」を押すと、アプリが終了します。しかし、それは空白のウィンドウを表示しており、私はそれを閉じるために3回押し戻す必要があります。

また、を起動するとA、次にB、次にC、そしてB再び...

期待される:

| |
| |
|B|
|A|
___

実際:

| |
|B|
|B|
|A|
___

onBackPressed()カスタマイズでオーバーライドする必要がありますか、それとも何か不足していますか?

回答:


279

ドキュメントを読むと、トランザクション名またはcommitによって提供されたIDに基づいてバックスタックをポップする方法があります。「一意のバックスタックエントリ」ロジックを変更および強化する可能性がある番号を追跡する必要がないため、名前を使用する方が簡単な場合あります。

必要なバックスタックエントリはあたり1つだけなのでFragment、バックステート名をフラグメントのクラス名にします(を介してgetClass().getName())。次に、を置き換えるときにFragmentpopBackStackImmediate()メソッドを使用します。trueを返す場合、バックスタックにフラグメントのインスタンスがあることを意味します。そうでない場合は、実際にフラグメント置換ロジックを実行します。

private void replaceFragment (Fragment fragment){
  String backStateName = fragment.getClass().getName();

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment);
    ft.addToBackStack(backStateName);
    ft.commit();
  }
}

編集

問題は、Aを起動してからBを起動し、[戻る]ボタンを押すと、Bが削除されてAが再開されることです。もう一度戻るボタンを押すと、アプリが終了します。しかし、それは空白のウィンドウを表示しており、それを閉じるには別のプレスが必要です。

これは、FragmentTransaction後でフラグメントを上にポップできるように、がバックスタックに追加されているためです。onBackPressed()バックスタックに含まれているのが1つだけの場合、これをすばやく修正して、アクティビティをオーバーライドして終了します。Fragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

重複するバックスタックエントリに関して、ポップされていない場合にフラグメントを置き換える条件ステートメントは、元のコードスニペットのものとは明らかに異なります。バックスタックがポップされたかどうかに関係なく、バックスタックに追加しています。

このようなものはあなたが望むものに近いはずです:

private void replaceFragment (Fragment fragment){
  String backStateName =  fragment.getClass().getName();
  String fragmentTag = backStateName;

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment, fragmentTag);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
  } 
}

表示されているときに同じフラグメントを選択すると、エントリが重複するため、条件が少し変更されました。

実装:

replaceFragment()コードで行ったように、更新されたメソッドを分解しないことを強くお勧めします。すべてのロジックはこのメソッドに含まれており、パーツを移動すると問題が発生する可能性があります。

これは、更新されたreplaceFragment()メソッドをクラスにコピーしてから変更する必要があることを意味します

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

だからそれは単に

replaceFragment (fragmentName);

編集#2

バックスタックが変更されたときにドロワーを更新するには、フラグメントで受け入れてクラス名を比較するメソッドを作成します。一致するものがあれば、タイトルと選択を変更します。また、を追加してOnBackStackChangedListener、有効なFragmentがある場合はupdateメソッドを呼び出すようにします。

たとえば、アクティビティのにonCreate()

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

そして他の方法:

private void updateTitleAndDrawer (Fragment fragment){
  String fragClassName = fragment.getClass().getName();

  if (fragClassName.equals(A.class.getName())){
    setTitle ("A");
    //set selected item position, etc
  }
  else if (fragClassName.equals(B.class.getName())){
    setTitle ("B");
    //set selected item position, etc
  }
  else if (fragClassName.equals(C.class.getName())){
    setTitle ("C");
    //set selected item position, etc
  }
}

これで、バックスタックが変更されるたびに、タイトルとチェックされた位置にvisibleが反映されますFragment


ご回答有難うございます。:)それは部分的に働いています。進行状況に合わせて質問を編集しました。ご覧ください)
Kaidul 2013

2
@RishabhSrivastava「ポップ」は通常、最上位のフラグメントを破棄することに注意してください。どのメソッドが最初に呼び出されるかは、状況によって異なります。フラグメントのライフサイクルをご覧ください。あなたのケースでOnBackstackChangedListenerは、正しいフラグメントを処理していることを保証できるように、私はを使用します。
A--C

2
@RishabhSrivastavaこれが私が提案した理由でOnBackstackChangedListenerもあります。
A--C

1
@AlexanderFarber正解です。私がこの答えを出したとき、私は考えていませんでしたinstanceof:)
A--C

2
あなたのソリューションは良さそうに聞こえますが、3つのフラグメントがあり、それらA-B-C-Bをクリックして1回押し戻すとA、に戻るのではなく、に戻りCます。これは、popBackStackImmediate指定されたタグをポップするだけでなく、上記のすべてをポップするためです。そこに何か仕事はありますか?
Raeglan、

10

この方法で問題を解決できると思います。

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}

この質問に最初に投稿された もの


このような回答がルールに反する場合は、この投稿で人々が見やすいようにしたかっただけです。ありがとう..
erluxman 2017年

ルールに違反しているかどうかは
わかり

1
3行目の用途は何ですか?-> manager.findFragmentByTag(タグ); それは何もしていないように見えます..
Rik van Velzen

3

ステップ1:アクティビティクラスとのインターフェースを実装する

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .............
        FragmentManager fragmentManager = getFragmentManager();           
        fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
    }

    private void switchFragment(Fragment fragment){            
      FragmentManager fragmentManager = getFragmentManager();
      fragmentManager.beginTransaction()
        .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
    }

    @Override
    public void onBackStackChanged() {
    FragmentManager fragmentManager = getFragmentManager();

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
            + fragmentManager.getBackStackEntryCount());

    int count = fragmentManager.getBackStackEntryCount();

    // when a fragment come from another the status will be zero
    if(count == 0){

        System.out.println("again loading user data");

        // reload the page if user saved the profile data

        if(!objPublicDelegate.checkNetworkStatus()){

            objPublicDelegate.showAlertDialog("Warning"
                    , "Please check your internet connection");

        }else {

            objLoadingDialog.show("Refreshing data..."); 

            mNetworkMaster.runUserSummaryAsync();
        }

        // IMPORTANT: remove the current fragment from stack to avoid new instance
        fragmentManager.removeOnBackStackChangedListener(this);

    }// end if
   }       
}

ステップ2:別のフラグメントを呼び出すときに、このメソッドを追加します。

String backStateName = this.getClass().getName();

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);

fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();

2

この質問に答えるのはかなり遅いですが、私はこの問題を自分で解決し、みんなと共有する価値があると考えました。

public void replaceFragment(BaseFragment fragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    final FragmentManager fManager = getSupportFragmentManager();
    BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
    transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);

    if (fragm == null) {  //here fragment is not available in the stack
        transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
        transaction.addToBackStack(fragment.getFragmentTag());
    } else { 
        //fragment was found in the stack , now we can reuse the fragment
        // please do not add in back stack else it will add transaction in back stack
        transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
    }
    transaction.commit();
}

そしてonBackPressed()

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}

1
@ X-Black ... BaseFragmentは、アプリケーションで使用されるすべてのフラグメントの基本クラスです。
Vivek Pratap Singh

0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

        if(getFragmentManager().getBackStackEntryCount()==0) {
            onResume();
        }    
    }
});

0

より簡単な解決策は、この行を変更することです

ft.replace(R.id.content_frame, A);ft.add(R.id.content_frame, A);

そしてあなたのXMLレイアウトの中で使用してください

  android:background="@color/white"
  android:clickable="true"
  android:focusable="true"

Clickable これは、ポインターデバイスでクリックしたり、タッチデバイスでタップしたりできることを意味します。

Focusableキーボードなどの入力デバイスからフォーカスを取得できることを意味します。キーボードなどの入力デバイスは、入力自体に基づいて入力イベントを送信するビューを決定できないため、フォーカスのあるビューに送信します。

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