Как возобновить фрагмент из BackStack, если он существует

Я изучаю, как использовать фрагменты. У меня есть три экземпляра Fragment, которые инициализируются в верхней части класса. Я добавляю фрагмент к следующему действию:

Объявление и инициализация:

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

Эти фрагменты работают исправно. Каждый фрагмент привязан к действию и без проблем сохраняется в фоновом стеке.

Поэтому, когда я запускаю A, C, а затем B, стек выглядит следующим образом:

| |
|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 будет возобновлено. и нажатие "назад" второй раз должно выйти из приложения. Но он показывает пустое окно, и я должен вернуться в третий раз, чтобы закрыть его.

Кроме того, когда я запускаю A, тогда B, затем C, затем B снова...

Ожидаемое:

| |
| |
|B|
|A|
___

Actual:

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

Должен ли я переопределить onBackPressed() с любой настройкой или я что-то пропустил?

Ответ 1

Читая документацию, есть способ извлечь задний стек на основе либо имени транзакции, либо идентификатора, предоставленного commit. Использование имени может быть проще, поскольку оно не должно требовать отслеживания числа, которое может измениться, и усиливает логику "уникального входа в стек назад".

Поскольку для каждого Fragment getClass().getName() только одна запись заднего стека, getClass().getName() имени обратного состояния имя класса Fragment (через getClass().getName()). Затем при замене Fragment используйте метод popBackStackImmediate(). Если он возвращает 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() - переопределение onBackPressed() и завершение Activity, если задний стек содержит только 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() как вы это делали в своем коде. Вся логика содержится в этом методе, и перемещение деталей может вызвать проблемы.

Это означает, что вы должны скопировать обновленный 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 и вызовите метод обновления, если есть допустимый фрагмент.

Например, в Activity 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.

Ответ 2

Я думаю, что этот метод может решить вашу проблему:

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

который был первоначально размещен в этом вопросе

Ответ 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();

Ответ 4

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

    @Override
    public void onBackStackChanged() {

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

Ответ 5

Более простым решением будет изменение этой линии

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 означает, что он может получить фокус от устройства ввода, такого как клавиатура. Устройства ввода, такие как клавиатуры, не могут решить, в какое представление отправлять свои события ввода, основываясь на самих вводах, поэтому они отправляют их в представление, которое имеет фокус.

Ответ 6

Я знаю, что уже поздно отвечать на этот вопрос, но я решил эту проблему сам и подумал, что стоит поделиться ею со всеми ".

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