Фрагменты onResume из заднего стека

Я использую пакет совместимости для использования фрагментов с Android 2.2. При использовании фрагментов и добавлении переходов между ними в backstack я хотел бы добиться того же поведения onResume действия, то есть всякий раз, когда фрагмент переводится на «передний план» (видимый для пользователя) после выхода из backstack, я бы хотел, чтобы во фрагменте активировался какой-то обратный вызов (например, для выполнения определенных изменений в общем ресурсе пользовательского интерфейса).

Я видел, что в структуре фрагмента нет встроенного обратного вызова. есть ли хорошая практика для этого?


person oriharel    schedule 28.06.2011    source источник
comment
Отличный вопрос. Я пытаюсь изменить заголовок панели действий в зависимости от того, какой фрагмент виден в качестве варианта использования для этого сценария, и мне кажется, что в API отсутствует обратный вызов.   -  person Manfred Moser    schedule 06.01.2012
comment
Ваша активность может установить заголовок, если он знает, какие фрагменты отображаются в любой момент. Также я полагаю, что вы могли бы сделать что-нибудь в onCreateView, которое будет вызываться для фрагмента после всплывающего сообщения.   -  person PJL    schedule 01.02.2012
comment
@PJL +1 По ссылке так и надо делать. Однако я предпочитаю путь слушателя   -  person momo    schedule 04.08.2013


Ответы (12)


Из-за отсутствия лучшего решения у меня это работает: предположим, у меня есть 1 действие (MyActivity) и несколько фрагментов, которые заменяют друг друга (только один виден за раз).

В MyActivity добавьте этого слушателя:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(Как видите, я использую пакет совместимости).

Реализация getListener:

private OnBackStackChangedListener getListener()
    {
        OnBackStackChangedListener result = new OnBackStackChangedListener()
        {
            public void onBackStackChanged() 
            {                   
                FragmentManager manager = getSupportFragmentManager();

                if (manager != null)
                {
                    MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);

                    currFrag.onFragmentResume();
                }                   
            }
        };

        return result;
    }

MyFragment.onFragmentResume() будет вызываться после нажатия "Назад". Однако несколько предостережений:

  1. Предполагается, что вы добавили все транзакции в backstack (используя FragmentTransaction.addToBackStack())
  2. Он будет активироваться при каждом изменении стека (вы можете хранить другие вещи в заднем стеке, например, анимацию), поэтому вы можете получить несколько вызовов для одного и того же экземпляра фрагмента.
person oriharel    schedule 28.06.2011
comment
Вы должны принять свой ответ как правильный, если он сработал для вас. - person powerj1984; 06.12.2011
comment
Как это работает с точки зрения идентификатора фрагмента, по которому вы запрашиваете? Чем он отличается для каждого фрагмента, а правый находится в задней стопке? - person Manfred Moser; 06.01.2012
comment
Не было бы проще просто использовать onResume () внутри фрагмента? - person Warpzit; 26.01.2012
comment
Почему Google до сих пор не добавил эту базовую функцию? Я пробовал все, но кажется, что и добавление, и замена фрагментов сильно сломаны - они иногда запускают то, что Google говорит, что они делают, но обычно нет. - person Adam; 22.05.2013
comment
@Warpzit этот метод не вызывается, и документы Android спокойно признают, что он никогда не будет вызываться (он вызывается только тогда, когда активность возобновляется - чего никогда не бывает, если эта активность уже активна) - person Adam; 22.05.2013
comment
onResume не называется. Добавление такого слушателя стека - лучшее решение, которое я знаю. - person SK9; 05.06.2013
comment
На данный момент это лучшее (единственное) решение - person momo; 04.08.2013
comment
Смешно, что мы должны это делать! Но рад, что кто-то другой смог найти эту функцию - person stevebot; 05.04.2014
comment
Вызывается OnResume (). Но у меня пустой экран. Есть ли другой способ решить эту проблему? - person kavie; 06.10.2014
comment
onResume () НЕ вызывается - person Marty Miller; 09.12.2014
comment
При возобновлении вызывается каждый раз, когда фрагмент помещается в контейнер. Однако он НЕ вызывается, когда вы открываете backstack, и фрагмент отображается снова. Единственное решение, которое я видел в работе, - это прослушивание прослушивателя заднего стека, как указано в этом ответе. - person Lo-Tan; 22.05.2015
comment
Замечательное решение! работает именно так, как мы хотим .. Спасибо! - person Meet Vora; 08.06.2016
comment
onFragmentResume не существует, используйте onResume. - person CoolMind; 12.10.2016
comment
Вы серьезно относитесь к этому ответу, это ваша работа? - person parvez rafi; 31.05.2017
comment
В этом методе onResume () будет вызываться дважды, пока текущий фрагмент добавляется в backstack. Один за onCreate (по жизненному циклу), другой из OnBackstackChangeListener (). - person Amit; 13.01.2020

Я немного изменил предложенное решение. У меня лучше работает вот так:

private OnBackStackChangedListener getListener() {
    OnBackStackChangedListener result = new OnBackStackChangedListener() {
        public void onBackStackChanged() {
            FragmentManager manager = getSupportFragmentManager();
            if (manager != null) {
                int backStackEntryCount = manager.getBackStackEntryCount();
                if (backStackEntryCount == 0) {
                    finish();
                }
                Fragment fragment = manager.getFragments()
                                           .get(backStackEntryCount - 1);
                fragment.onResume();
            }
        }
    };
    return result;
}
person Brotoo25    schedule 11.09.2013
comment
Объясните, пожалуйста, разницу и зачем это использовать. - person Math chiller; 12.09.2013
comment
Разница между моим решением и предыдущим заключается в том, что при каждом изменении фрагмента, таком как добавление, замена или возврат в куче фрагментов, будет вызываться метод onResume верхнего фрагмента. В этом методе нет жесткого кода фрагмента. По сути, он вызывает onResume () во фрагменте, который вы просматриваете, при каждом изменении, независимо от того, было ли оно уже загружено в память или нет. - person Brotoo25; 03.12.2013
comment
Нет записи для метода с именем getFragments() в SupportFragmentManager ... -1: - / - person Protostome; 07.04.2014
comment
Вызывается OnResume (). Но у меня пустой экран. Есть ли другой способ решить эту проблему? - person kavie; 06.10.2014
comment
Я думаю, что это приводит к исключению ArrayOutOfBounds, если backStackEntryCount равно 0. Я ошибаюсь? Пытался отредактировать сообщение, но оно было отклонено. - person Mike T; 10.03.2016
comment
Для меня это решение иногда приводит к исключению ArrayOutOfBounds. Посмотрите на мой ответ ниже, чтобы уловить только конкретный фрагмент. - person Quan Nguyen; 17.03.2016
comment
Хотя я переписал этот метод для себя, спасибо за OnBackStackChangedListener. - person CoolMind; 02.08.2016
comment
@Protostome у вас должен быть установлен уровень API 26, чтобы этот вызов работал - person Maurizio; 05.12.2017

После popStackBack() вы можете использовать следующий обратный вызов: onHiddenChanged(boolean hidden) внутри вашего фрагмента

person user2230304    schedule 16.04.2015
comment
Похоже, это не работает с фрагментами совместимости приложений. - person Lo-Tan; 22.05.2015
comment
Вот что я использовал. Спасибо! - person Albert Vila Calvo; 22.02.2016
comment
Самое простое решение! Просто добавьте проверку, виден ли фрагмент в данный момент, и вуаля, вы можете установить заголовок панели приложений. - person EricH206; 05.01.2017

В следующем разделе Android Developers описывается механизм связи Создание обратных вызовов событий для действия. Процитировать из него строчку:

Хороший способ сделать это - определить интерфейс обратного вызова внутри фрагмента и потребовать, чтобы активность хоста реализовывала его. Когда действие получает обратный вызов через интерфейс, оно может при необходимости делиться информацией с другими фрагментами макета.

Изменить: фрагмент имеет onStart(...), который вызывается, когда фрагмент виден пользователю. Аналогично onResume(...), когда он виден и активно работает. Они привязаны к своим аналогам активности. Вкратце: используйте onResume()

person PJL    schedule 28.06.2011
comment
Я просто ищу метод в классе Fragment, который активируется всякий раз, когда он отображается пользователю. будь то из-за FragmentTransaction.add () / replace () или FragmentManager.popBackStack (). - person oriharel; 28.06.2011
comment
@oriharel См. обновленный ответ. Когда активность загружена, вы получите различные вызовы: onAttach, onCreate, onActivityCreated, onStart, onResume. Если вы вызвали `setRetainInstance (true) ', тогда вы не получите onCreate, когда фрагмент повторно отображается при обратном нажатии. - person PJL; 28.06.2011
comment
onStart () никогда не вызывается при нажатии Back между фрагментами в одном действии. Я попробовал большинство обратных вызовов в документации, и ни один из них не вызывается в таком сценарии. см. мой ответ для обходного пути. - person oriharel; 28.06.2011
comment
хм с compat lib v4 Я вижу onResume для моего фрагмента. Мне действительно нравится ответ @oriharel, поскольку он различает то, что он становится видимым через popBackStack, а не просто выводится на передний план. - person PJL; 01.02.2012

Если фрагмент помещается в стек, Android просто уничтожает его представление. Сам экземпляр фрагмента не уничтожается. Простой способ начать - это прослушать событие onViewCreated и поместить туда логику onResume ().

boolean fragmentAlreadyLoaded = false;
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);

        if (savedInstanceState == null && !fragmentAlreadyLoaded) {
            fragmentAlreadyLoaded = true;

            // Code placed here will be executed once
        }

        //Code placed here will be executed even when the fragment comes from backstack
    }
person Franjo    schedule 01.11.2014

В моей деятельности onCreate ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Используйте этот метод, чтобы поймать определенный фрагмент и вызвать onResume ()

private FragmentManager.OnBackStackChangedListener getListener()
    {
        FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
        {
            public void onBackStackChanged()
            {
                Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
                if (currentFragment instanceof YOURFRAGMENT) {
                    currentFragment.onResume();
                }
            }
        };

        return result;
    }
person Quan Nguyen    schedule 17.03.2016

Немного доработан и завернут в менеджерское решение.

О чем нужно помнить. FragmentManager - это не синглтон, он управляет только фрагментами в Activity, поэтому в каждом действии он будет новым. Кроме того, это решение пока не учитывает ViewPager, который вызывает метод setUserVisibleHint (), помогающий контролировать видимость фрагментов.

Не стесняйтесь использовать следующие классы при решении этой проблемы (использует инъекцию Dagger2). Вызов в действии:

//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager(); 
myFragmentBackstackStateManager.apply(fragmentManager);

FragmentBackstackStateManager.java:

@Singleton
public class FragmentBackstackStateManager {

    private FragmentManager fragmentManager;

    @Inject
    public FragmentBackstackStateManager() {
    }

    private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
        @Override
        public void onFragmentPushed(Fragment parentFragment) {
            parentFragment.onPause();
        }

        @Override
        public void onFragmentPopped(Fragment parentFragment) {
            parentFragment.onResume();
        }
    };

    public FragmentBackstackChangeListenerImpl getListener() {
        return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
    }

    public void apply(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
        fragmentManager.addOnBackStackChangedListener(getListener());
    }
}

FragmentBackstackChangeListenerImpl.java:

public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {

    private int lastBackStackEntryCount = 0;
    private final FragmentManager fragmentManager;
    private final BackstackCallback backstackChangeListener;

    public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
        this.fragmentManager = fragmentManager;
        this.backstackChangeListener = backstackChangeListener;
        lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
    }

    private boolean wasPushed(int backStackEntryCount) {
        return lastBackStackEntryCount < backStackEntryCount;
    }

    private boolean wasPopped(int backStackEntryCount) {
        return lastBackStackEntryCount > backStackEntryCount;
    }

    private boolean haveFragments() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList != null && !fragmentList.isEmpty();
    }


    /**
     * If we push a fragment to backstack then parent would be the one before => size - 2
     * If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
     *
     * @return fragment that is parent to the one that is pushed to or popped from back stack
     */
    private Fragment getParentFragment() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList.get(Math.max(0, fragmentList.size() - 2));
    }

    @Override
    public void onBackStackChanged() {
        int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
        if (haveFragments()) {
            Fragment parentFragment = getParentFragment();

            //will be null if was just popped and was last in the stack
            if (parentFragment != null) {
                if (wasPushed(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPushed(parentFragment);
                } else if (wasPopped(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPopped(parentFragment);
                }
            }
        }

        lastBackStackEntryCount = currentBackStackEntryCount;
    }
}

BackstackCallback.java:

public interface BackstackCallback {
    void onFragmentPushed(Fragment parentFragment);

    void onFragmentPopped(Fragment parentFragment);
}
person AAverin    schedule 22.09.2015

Это правильный ответ, который вы можете вызвать onResume (), если фрагмент прикреплен к действию. В качестве альтернативы вы можете использовать onAttach и onDetach

person iamlukeyb    schedule 01.10.2014
comment
Не могли бы вы опубликовать пример, пожалуйста? - person kavie; 06.10.2014

onResume () для фрагмента отлично работает ...

public class listBook extends Fragment {

    private String listbook_last_subtitle;
...

    @Override
       public void onCreate(Bundle savedInstanceState) {

        String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
        listbook_last_subtitle = thisFragSubtitle;
       }
...

    @Override
        public void onResume(){
            super.onResume();
            getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
        }
...
person Roshan Poudyal    schedule 14.10.2014

public abstract class RootFragment extends Fragment implements OnBackPressListener {

 @Override
 public boolean onBackPressed() {
  return new BackPressImpl(this).onBackPressed();
 }

 public abstract void OnRefreshUI();

}


public class BackPressImpl implements OnBackPressListener {

 private Fragment parentFragment;

 public BackPressImpl(Fragment parentFragment) {
  this.parentFragment = parentFragment;
 }

 @Override
 public boolean onBackPressed() {
  ((RootFragment) parentFragment).OnRefreshUI();
 }
}

и окончательно вытяните фрагмент из RootFragment, чтобы увидеть эффект

person Luu Quoc Thang    schedule 24.03.2016

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

@Override
public void onResume() {
    super.onResume();
    // Get/Backup current title
    mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
            .getTitle();
    // Set new title
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(R.string.this_fragment_title);
}

@Override
public void onDestroy() {
    // Set title back
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(mTitle);

    super.onDestroy();
}
person Mahendran Candy    schedule 19.05.2016

Я использовал enum FragmentTags для определения всех моих классов фрагментов.

TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)

передать FragmentTags.TAG_FOR_FRAGMENT_A.name() как тег фрагмента.

а теперь на

@Override
public void onBackPressed(){
   FragmentManager fragmentManager = getFragmentManager();
   Fragment current
   = fragmentManager.findFragmentById(R.id.fragment_container);
    FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());

  switch(fragmentTag){
    case TAG_FOR_FRAGMENT_A:
        finish();
        break;
   case TAG_FOR_FRAGMENT_B:
        fragmentManager.popBackStack();
        break;
   case default: 
   break;
}
person Ali    schedule 20.01.2017