it-swarm.com.ru

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

Актуально:

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

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

123
Kaidul

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

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

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

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.

251
A--C

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

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

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

6
erluxman

Шаг 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();
3
Vinod Joshi

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

ft.replace(R.id.content_frame, A);to 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
Vivek Pratap Singh
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

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