Как возобновить фрагмент из 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|
___

Edit: (предлагается 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|
___

фактически:

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

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

4 114

4 ответа:

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

так как вы хотите только одну запись обратно стека в 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();
  }
}

EDIT

проблема - когда я запускаю A, а затем B, а затем нажмите кнопку Назад, B удаляется и восстанавливается. и снова нажать кнопку Назад следует выйдите из приложения. Но он показывает пустое окно и нужен другой нажимать закрыть его.

это так 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);

EDIT #2

чтобы обновить ящик при изменении заднего стека, создайте метод, который принимает во фрагменте и сравнивает имена классов. Если что-то совпадает, измените заголовок и выбор. Также добавьте OnBackStackChangedListener и пусть он вызовет ваш метод обновления, если есть допустимый фрагмент.

например, в деятельности 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.

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

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


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    manager.findFragmentByTag ( tag );
    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 ();
    }
}

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

Шаг 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();
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

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