web-dev-qa-db-ja.com

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

フラグメントの使い方を学んでいます。クラスの先頭で初期化される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();

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

AC、そしてBを起動すると、スタックは次のようになります。

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

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

しかし、フラグメントAを2回起動すると、バックスタックから再開するのではなく、バックスタックの先頭に追加されます。

| |
|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が再開されることです。もう一度「戻る」を押すとアプリは終了します。しかし、それは空白のウィンドウを表示しています、そして私はそれを閉じるためにもう一度押しなければなりません。

また、ABCBの順に起動したときも、.

予想されるもの

| |
| |
|B|
|A|
___

実績:

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

onBackPressed()をカスタマイズして上書きする必要がありますか、それとも何かが足りないのでしょうか。

133
Kaidul

documentation を読んで、トランザクション名かcommitによって提供されたidのどちらかに基づいてバックスタックをポップする方法があります。名前を使用すると、変更される可能性がある数を追跡して「固有のバックスタックエントリ」ロジックを強化する必要がないため、簡単に使用できます。

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

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();
  }
}

EDIT

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

これは、後でフラグメントをポップできるようにFragmentTransactionがバックスタックに追加されているためです。これに対する簡単な修正は、onBackPressed()をオーバーライドし、バックスタックに1つのFragmentしか含まれていない場合はActivityを終了することです。

@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を追加して、有効なフラグメントがある場合はそれをあなたの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
  }
}

これで、バックスタックが変わるたびに、タイトルとチェック位置は目に見えるFragmentを反映します。

259
A--C

この方法であなたの問題は解決すると思います。

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 ();
    }
}

これはもともとに投稿された この質問

8
erluxman

Step 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
   }       
}

Step 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();
3
Vinod Joshi

より簡単な解決策はこの線を変えるでしょう

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

0
Hossam Hassan

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

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();
    }
}
0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

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