Подтвердить что ты не робот

Проблемы с стопкой Android Fragment back

У меня возникла серьезная проблема с тем, как работает backstack андроидного фрагмента, и будет очень благодарен за любую помощь, которая предлагается.

Представьте, что у вас есть 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: [email protected]/details -->
</fragment>
<FrameLayout
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"

        />



Обновление Это код, который я использую для создания навй-иерархией

    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]

Большое спасибо

4b9b3361

Ответ 1

Объяснение: что здесь происходит?

Если учесть, что .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 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() method:

Ответ 2

Right!!! после много вытягивания волос я наконец-то разработал, как сделать эту работу должным образом.

Кажется, что фрагмент [3] не удаляется из представления, когда нажата кнопка назад, поэтому вам нужно сделать это вручную!

Прежде всего, не используйте replace(), а вместо этого используйте remove и добавьте отдельно. Кажется, что replace() не работает должным образом.

Следующая часть этого метода переопределяет метод 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();

}

Надеюсь, это поможет!

Ответ 3

Прежде всего спасибо @Arvis за объяснение в глазах.

Я предпочитаю другое решение для принятого ответа здесь для этой проблемы. Мне не нравится возиться с переопределяющим обратным поведением больше, чем это абсолютно необходимо, и когда я попытался добавить и удалить фрагменты самостоятельно без по умолчанию назад, когда вы нажимаете кнопку "назад", я нашел себя в фрагменте hell:) Если вы. добавьте 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, если вы перейдете к f3, так что там будет небольшой сбой.

Это на самом деле все, что вам нужно сделать, не нужно переопределять поведение предыдущего стека...

Ответ 4

Я знаю, что это старый quetion, но у меня такая же проблема и исправить ее вот так:

Сначала добавьте фрагмент 1 в 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);

Надеюсь, это поможет кому-то:)

Ответ 5

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

Для ленивых разработчиков вокруг. Мое решение состоит в том, чтобы всегда добавлять транзакции в backstack и выполнять дополнительный 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();
        }
      }
    }
  });
}

Ответ 6

Если вы боретесь с addToBackStack() и popBackStack(), просто используйте

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

В вашей деятельности В OnBackPressed() найдите мерцание по тегу, а затем сделайте свой материал

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

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

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

Ответ 7

Я думаю, когда я прочитал вашу историю, что [3] также находится в задней части. Это объясняет, почему вы видите, как он вспыхивает.

Решение было бы никогда не устанавливать [3] в стеке.

Ответ 8

У меня была аналогичная проблема, когда у меня было 3 последовательных фрагмента в том же Activity [M1.F0] → [M1.F1] → [M1.F2], за которым следует вызов к новому Activity [M2]. Если пользователь нажал кнопку в [M2], мне захотелось вернуться к [M1, F1] вместо [M1, F2], что уже вызвало поведение после нажатия.

Для этого я удаляю [M1, F2], вызывное шоу на [M1, F1], фиксирует транзакцию, а затем добавляю [M1, F2] назад, вызывая его с помощью hide. Это удалило дополнительное заднее нажатие, которое в противном случае было бы оставлено.

// 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 при нажатии Back Key.   Мой код:

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