Як відновити фрагмент із BackStack, якщо він існує


151

Я вчусь використовувати фрагменти. У мене є три екземпляри 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()будь-яку настройку чи мені щось не вистачає?

Відповіді:


279

Читаючи документацію , є спосіб вивести задній стек на основі або назви транзакції, або ідентифікатора, наданого комітом. Використання імені може бути простішим, оскільки воно не повинно вимагати відстеження числа, яке може змінюватися, і підсилює логіку "унікального введення зворотного стека".

Оскільки ви хочете лише один запис із зворотного стека на кожне Fragment, зробіть назву зворотного стану ім'я класу фрагмента (через getClass().getName()). Потім замінюючи a 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()та завершення діяльності, якщо задній стек містить лише 1Fragment

@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 і запропонуйте йому викликати ваш метод оновлення, якщо є дійсний фрагмент.

Наприклад, у розділі Діяльність 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.


Дякую за вашу відповідь. :) Це працює частково. Я відредагував своє запитання з прогресом. Будь ласка, подивіться)
Kaidul

2
@RishabhSrivastava майте на увазі, що "попс" зазвичай знищує найвищий фрагмент. Від того, який метод називається спочатку, залежить - подивіться на життєвий цикл фрагмента. Для вашого випадку я використовую OnBackstackChangedListenerтак, щоб ви могли гарантувати, що ви маєте справу з правильним фрагментом.
A - C

2
@RishabhSrivastava Саме тому я і запропонував це OnBackstackChangedListener.
A - C

1
@AlexanderFarber Ви маєте рацію. Ще коли я зробив цю відповідь, я не думав про це instanceof:)
A - C

2
Ваше рішення звучить добре, але якщо у вас є три фрагменти, натисніть на них A-B-C-Bі натисніть один раз назад, ви повернетесь до, Aа не назад C. Це тому, що popBackStackImmediateне тільки спливає даний тег, але і все вище. Чи є там якась робота?
Реглан

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

Я не впевнений, чи це проти правил (напевно, ні), але це, безумовно, корисно
Катір

1
Яке використання третього рядка? -> manager.findFragmentByTag (тег); Схоже, це нічого не робить ..
Рік ван

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

І в onBackPress ()

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

1
@ X-Black ... BaseFragment - базовий клас для кожного фрагмента, який використовується в додатку.
Вівек Пратап Сінгх

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означає, що він може отримати фокус від такого пристрою введення, як клавіатура. Пристрої введення, такі як клавіатури, не можуть вирішити, якому виду надсилати події введення на основі самих входів, тому вони надсилають їх до подання, на якому є фокус.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.