Проблеми зі зворотним стеком Android Fragment


121

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

Уявіть, що у вас є 3 фрагменти

[1] [2] [3]

Я хочу, щоб користувач міг орієнтуватися, [1] > [2] > [3]але на зворотному шляху (натискаючи кнопку назад) [3] > [1].

Як я міг би уявити, це буде досягнуто, не викликаючи addToBackStack(..)при створенні транзакції, яка приносить фрагмент [2]до власника фрагмента, визначеного в XML.

Реальність цього виглядає так, що якщо я не хочу [2]з’являтися знову, коли користувач натискає кнопку назад [3], я не повинен дзвонити addToBackStackв транзакції, що показує фрагмент [3]. Це здається абсолютно протиінтуїтивним (можливо, походить із світу iOS).

У будь-якому випадку, якщо я це роблю таким чином, коли я йду [1] > [2]і натискаю назад, я повертаюся назад, [1]як очікувалося.

Якщо я іду, [1] > [2] > [3]а потім натискаю назад, я стрибаю назад [1](як очікувалося). Зараз дивна поведінка трапляється, коли я намагаюся [2]знову перестрибнути [1]. Перш за все [3], коротко відображається, перш ніж [2]потрапить у поле перегляду. Якщо я натискаю назад у цей момент [3], відображається, і якщо я натискаю ще раз, програма закриває.

Хтось може допомогти мені зрозуміти, що тут відбувається?


І ось основний xml-файл для моєї основної діяльності:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >

<fragment
        android:id="@+id/headerFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        class="com.fragment_test.FragmentControls" >
    <!-- Preview: layout=@layout/details -->
</fragment>
<FrameLayout
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"

        />



Оновити Це код, який я використовую для побудови на nav heirarchy

    Fragment frag;
    FragmentTransaction transaction;


    //Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack
    frag = new Fragment1();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();

    //Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack
    frag = new Fragment2();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();


    //Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2] 
    frag = new Fragment3();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();


     //END OF SETUP CODE-------------------------
    //NOW:
    //Press back once and then issue the following code:
    frag = new Fragment2();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();

    //Now press back again and you end up at fragment [3] not [1]

Велике дякую


але фрагмент перекривається, коли я повертаюсь із фрагмента С на фрагмент А.
Приянка

У мене є те саме питання, як виправити це?
Приянка

зверніться до моїх анс .. це може вам допомогти < stackoverflow.com/questions/14971780/… >
доктор Халі

Відповіді:


203

Пояснення: що тут відбувається?

Якщо ми маємо на увазі, що .replace()це дорівнює .remove().add()тому, що ми знаємо в документації:

Замініть наявний фрагмент, який було додано до контейнера. Це по суті те саме, що викликати remove(Fragment)всі додані в даний час фрагменти, які були додані з тими ж, containerViewIdа потім add(int, Fragment, String)з тими ж аргументами, наведеними тут.

то те, що відбувається, є таким (я додаю числа до фрагменту, щоб зробити його більш зрозумілим):

// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1)  // frag1 on view

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null)  // frag2 on view

// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3)  // frag3 on view

(тут починають відбуватися всі оманливі речі)

Пам'ятайте, що .addToBackStack()це збереження лише транзакції, а не фрагмент як сам! Отже, тепер ми маємо frag3на схемі:

< press back button >
// System pops the back stack and find the following saved back entry to be reversed:
// [Transaction.remove(frag1).add(frag2)]
// so the system makes that transaction backward!!!
// tries to remove frag2 (is not there, so it ignores) and re-add(frag1)
// make notice that system doesn't realise that there's a frag3 and does nothing with it
// so it still there attached to view
Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING)

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag3).add(frag2).addToBackStack(null)  //frag2 on view

< press back button >
// system makes saved transaction backward
Transaction.remove(frag2).add(frag3) //frag3 on view

< press back button >
// no more entries in BackStack
< app exits >

Можливе рішення

Подумайте про те, FragmentManager.BackStackChangedListenerщоб реалізувати, щоб спостерігати за змінами заднього стека та застосувати свою логіку в onBackStackChanged()методі:


Гарне пояснення @arvis. Однак, як ми можемо запобігти такій поведінці, не вдаючись до нерозумних способів, таких як DexterMoon або Nemanja, використовуючи popBackStack, який показує Фрагмент під час відтворення анімації переходу?
momo

@momo ви можете реалізувати, FragmentManager.BackStackChangedListenerщоб спостерігати за зміною задньої стеки. Контролюйте всі ваші транзакції з onBackStackChanged()методом і дійте за необхідності: напр. простежити кількість транзакцій у BackStack; перевірити конкретну транзакцію на ім’я ( FragmentTransaction addToBackStack (String name)) тощо
Arvis,

Дякую за відповідь. Я фактично спробував це раніше, зареєстрував слухача і вилучив фрагмент з onBackstackChange. У той час як перехідний перехід грає, фрагмент стає порожньою білою зоною. Я здогадуюсь, що метод спрацьовує при запуску анімації, а не тоді, коли закінчується ...
momo

4
Уникайте використання зворотних стеків! це не дуже допомагає з загальною ефективністю! використовуйте звичайну заміну () або ще краще видаляйте / додайте щоразу, коли хочете перейти!
stack_ved

@Arvis може у вас допомогти мені отримати те саме питання .... кількість стеків 0, але все-таки мій фрагмент видно?
Ерум

33

Правильно !!! після довгого начісування волосся я нарешті розробив, як зробити цю роботу належним чином.

Схоже, що фрагмент [3] не видаляється з виду при натисканні на спинку, тому це потрібно робити вручну!

Перш за все, не використовуйте заміну (), але замість цього використовуйте видалення та додавання окремо. Схоже, що заміна () не працює належним чином.

Наступна частина до цього - це перестановка методу onKeyDown і видалення поточного фрагмента кожного разу, коли натискається кнопка "назад".

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    if (keyCode == KeyEvent.KEYCODE_BACK)
    {
        if (getSupportFragmentManager().getBackStackEntryCount() == 0)
        {
            this.finish();
            return false;
        }
        else
        {
            getSupportFragmentManager().popBackStack();
            removeCurrentFragment();

            return false;
        }



    }

    return super.onKeyDown(keyCode, event);
}


public void removeCurrentFragment()
{
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

    Fragment currentFrag =  getSupportFragmentManager().findFragmentById(R.id.detailFragment);


    String fragName = "NONE";

    if (currentFrag!=null)
        fragName = currentFrag.getClass().getSimpleName();


    if (currentFrag != null)
        transaction.remove(currentFrag);

    transaction.commit();

}

Сподіваюся, це допомагає!


це працює, але це не може бути використане в моєму проекті, оскільки фрагмент перезавантажується! будь-які пропозиції?
TharakaNirmana

Але, якщо я це роблю. Я отримую порожній екран, коли повертаюся з C до A за фрагментом C.
Nigam Patro

Я підозрюю, що відповідь @Arvis має трохи підкреслити вашу проблему
Кріс Бірч

16

Перш за все дякую @Arvis за пояснення відкриття очей.

Я віддаю перевагу іншому вирішенню прийнятої відповіді тут на цю проблему. Мені не подобається возитися з переважаючою поведінкою спини більше, ніж абсолютно необхідно, і коли я намагався самостійно додавати та видаляти фрагменти без типового зворотного стека, коли натискається кнопка "назад", я знайшов себе у пеклі фрагмента :) Якщо ви. додайте f2 над f1, коли ви виймете його, f1 не викликає жодного з методів зворотного виклику, таких як onResume, onStart тощо, і це може бути дуже прикро.

Так чи інакше це роблю:

В даний час на дисплеї є лише фрагмент f1.

f1 -> f2

Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();

тут нічого незвичайного. Тоді як у фрагменті f2 цей код приведе вас до фрагмента f3.

f2 -> f3

Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

Я не впевнений, читаючи документи, чи це повинно працювати, цей метод транзакцій попінгу є асинхронним, і, можливо, кращим способом було б викликати popBackStackImmediate (). Але наскільки я можу сказати на своїх пристроях, це працює бездоганно.

Цією альтернативою буде:

final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

Тут насправді буде короткий перехід до f1 beofre, що рухається до f3, тому невеликий збій там.

Це насправді все, що вам потрібно зробити, не потрібно переохоплювати поведінку заднього стека ...


6
але глюк, це проблема
Zyoo

Це здається менш "хак-ей", ніж прослуховування змін BackStack. Дякую.
ahaisting

13

Я знаю, що це стара запитання, але у мене виникла та сама проблема і виправити її так:

Спочатку додайте Fragment1 до BackStack з назвою (наприклад, "Frag1"):

frag = new Fragment1();

transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();

І тоді, коли ви хочете повернутися до Fragment1 (навіть після додавання 10 фрагментів над ним), просто зателефонуйте popBackStackImmediate з назвою:

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

Сподіваюся, це комусь допоможе :)


1
Чудова відповідь. Мені довелося використовувати getSupportFragmentManager (). PopBackStackImmediate ("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); щоб змусити його працювати у моєму випадку використання
eliasbagley

1
Куди мені треба поставити цей код ???? getSupportFragmentManager (). popBackStackImmediate ("Frag1", 0); На MainActivty або onBackPress
pavel

не працює. :( це мій код; у моєму випадку Фрагмент перекривається. він відкриває Фрагмент А, але Фрагмент А перекривається на Фрагмент В.
Приянка

FragmentManager fragmentManager = getActivity (). GetSupportFragmentManager (); fragmentManager.popBackStack (FragmentA.class.getName (), FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction (); FragmentE фрагментE = новий FragmentE (); fragmentTransaction.replace (R.id.fragment_content, fragmentE, fragmentE.getClass (). getName ()); fragmentTransaction.commit ();
Приянка

5

Після відповіді @Arvis я вирішив копати ще глибше, і я написав технічну статтю про це тут: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- через-за лаштунками-кошмар-в-андроїді /

Для ледачих розробників навколо. Моє рішення полягає у тому, щоб завжди додавати транзакції до рюкзака та виконувати додаткові, FragmentManager.popBackStackImmediate()коли потрібно (автоматично).

У коду дуже мало рядків коду, і, на моєму прикладі, я хотів пропустити з C на A, не відскочивши назад на "B", якщо користувач не заглибився в backstack (наприклад, з C переходить до D).

Отже, доданий код буде працювати так: A -> B -> C (назад) -> A&A -> B -> C -> D (назад) -> C (назад) -> B (назад) -> A

де

fm.beginTransaction().replace(R.id.content, new CFragment()).commit()

були видані від "B" до "C", як у питанні.

Ок, добре ось код :)

public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) {
  final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1;

  fragmentManager.beginTransaction()
      .replace(R.id.content, fragment, tag)
      .addToBackStack(tag)
      .commit();

  fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
      int nowCount = fragmentManager.getBackStackEntryCount();
      if (newBackStackLength != nowCount) {
        // we don't really care if going back or forward. we already performed the logic here.
        fragmentManager.removeOnBackStackChangedListener(this);

        if ( newBackStackLength > nowCount ) { // user pressed back
          fragmentManager.popBackStackImmediate();
        }
      }
    }
  });
}

1
аварія на цьому рядку fragmentManager.popBackStackImmediate (); помилка: java.lang.IllegalStateException: FragmentManager вже виконує транзакції на com.example.myapplication.FragmentA $ 2.onBackStackChanged (FragmentA.java:43)
Priyanka,

1

Якщо ви боретеся з addToBackStack () та popBackStack (), просто використовуйте

FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`

У своїй діяльності InBackPress () дізнайтеся фрагмент за тегом, а потім виконайте свої речі

Fragment home = getSupportFragmentManager().findFragmentByTag("Home");

if (home instanceof HomeFragment && home.isVisible()) {
    // do you stuff
}

Для отримання додаткової інформації https://github.com/DattaHujare/NavigationDrawer Я ніколи не використовую addToBackStack () для обробки фрагменту.


0

Я думаю, коли я читаю твою історію, що [3] також знаходиться на задньому плані. Це пояснює, чому ви бачите, як він спалахує.

Рішенням було б ніколи не встановлювати [3] на стеку.


Привіт jdekei Дякую за ваш внесок. Проблема полягає в тому, що я не бачу, куди додаю [3] до задньої частини. Я додав ще один фрагмент коду, який демонструє (програмно) саме ту навігацію, яку я виконував за допомогою кнопок.
Кріс Беріх

Мені це теж допомагає, але я використовую з вас тільки deleteCurFragment і трохи іншу перевірку фрагментів. Замінити метод працює для мене чудово, можливо, це s old method issue but now itдобре. Спасибі
Віктору В.

0

У мене був аналогічний випуск, коли у мене були 3 послідовних фрагменти в тому ж Activity[M1.F0] -> [M1.F1] -> [M1.F2], за яким пішов дзвінок до нового Activity[M2]. Якщо користувач натискав кнопку в [M2], я хотів повернутися до [M1, F1] замість [M1, F2], що вже робило поведінку зворотного натискання.

Для цього я видаляю [M1, F2], показую дзвінки на [M1, F1], здійснюю транзакцію, а потім додаю [M1, F2] назад, викликаючи її приховати. Це видалило додатковий задній прес, який інакше залишився б позаду.

// Remove [M1.F2] to avoid having an extra entry on back press when returning from M2
final FragmentTransaction ftA = fm.beginTransaction();
ftA.remove(M1F2Fragment);
ftA.show(M1F1Fragment);
ftA.commit();
final FragmentTransaction ftB = fm.beginTransaction();
ftB.hide(M1F2Fragment);
ftB.commit();

Привіт Після цього коду: я не в змозі побачити значення Fragment2 при натисканні клавіші Назад. Мій код:

FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.frame, f1);
ft.remove(f1);

ft.add(R.id.frame, f2);
ft.addToBackStack(null);

ft.remove(f2);
ft.add(R.id.frame, f3);

ft.commit();

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event){

        if(keyCode == KeyEvent.KEYCODE_BACK){
            Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);
            FragmentTransaction transaction = getFragmentManager().beginTransaction();

            if(currentFrag != null){
                String name = currentFrag.getClass().getName();
            }
            if(getFragmentManager().getBackStackEntryCount() == 0){
            }
            else{
                getFragmentManager().popBackStack();
                removeCurrentFragment();
            }
       }
    return super.onKeyDown(keyCode, event);
   }

public void removeCurrentFragment()
    {
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);

        if(currentFrag != null){
            transaction.remove(currentFrag);
        }
        transaction.commit();
    }

0

executePendingTransactions(), commitNow()не працював (

Працював в androidx (jetpack).

private final FragmentManager fragmentManager = getSupportFragmentManager();

public void removeFragment(FragmentTag tag) {
    Fragment fragmentRemove = fragmentManager.findFragmentByTag(tag.toString());
    if (fragmentRemove != null) {
        fragmentManager.beginTransaction()
                .remove(fragmentRemove)
                .commit();

        // fix by @Ogbe
        fragmentManager.popBackStackImmediate(tag.toString(), 
            FragmentManager.POP_BACK_STACK_INCLUSIVE);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.