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

96

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

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

Орихарель
источник
13
Отличный вопрос. Я пытаюсь изменить заголовок панели действий в зависимости от того, какой фрагмент отображается в качестве варианта использования для этого сценария, и мне кажется, что в API отсутствует обратный вызов.
Manfred Moser
3
Ваша активность может установить заголовок, если он знает, какие фрагменты отображаются в любой момент. Также я полагаю, вы могли бы сделать что-то, onCreateViewчто будет вызываться для фрагмента после попа.
PJL
1
@PJL +1 По ссылке так и надо делать. Однако я предпочитаю способ слушателя
momo

Ответы:

116

Из-за отсутствия лучшего решения у меня работает следующее: Предположим, у меня есть 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. Он будет активироваться при каждом изменении стека (вы можете хранить другие вещи в заднем стеке, такие как анимация), поэтому вы можете получить несколько вызовов для одного и того же экземпляра фрагмента.
Орихарель
источник
3
Вы должны принять свой ответ как правильный, если он сработал для вас.
powerj1984 06
7
Как это работает с точки зрения идентификатора фрагмента, по которому вы запрашиваете? Чем он отличается для каждого фрагмента, а правый находится в задней стопке?
Manfred Moser
2
@Warpzit, этот метод не вызывается, и документация по Android спокойно признает, что он никогда не будет вызван (он вызывается только при возобновлении активности - а этого никогда не происходит, если эта активность уже активна)
Адам
2
Смешно, что мы должны это делать! Но рад, что кто-то другой смог найти эту «особенность»
stevebot
3
onResume () НЕ вызывается
Марти Миллер
33

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

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;
}
Brotoo25
источник
1
пожалуйста, объясните разницу и зачем это использовать.
Math chiller
2
Разница между моим решением и предыдущим заключается в том, что при каждом изменении фрагмента, таком как добавление, замена или возврат в стопку фрагментов, будет вызываться метод onResume верхнего фрагмента. В этом методе нет жесткого кода фрагмента. В основном он вызывает onResume () во фрагменте, который вы просматриваете, при каждом изменении, независимо от того, было ли оно уже загружено в память или нет.
Brotoo25 03
9
Нет записи для метода, вызываемого getFragments()в SupportFragmentManager ... -1: - /
Protostome
1
Вызывается OnResume (). Но у меня пустой экран. Есть ли другой способ решить эту проблему?
Кави
Я думаю, что это приводит к исключению ArrayOutOfBounds, если backStackEntryCount равно 0. Я ошибаюсь? Пытался отредактировать сообщение, но оно было отклонено.
Mike T
6

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

user2230304
источник
2
Это не работает с фрагментами совместимости приложений.
Ло-Тан
Вот что я использовал. Благодарность!
Альберт Вила Кальво,
Самое простое решение! Просто добавьте проверку, виден ли фрагмент в данный момент, и вуаля, вы можете установить заголовок панели приложений.
EricH206
4

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

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

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

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

Если фрагмент помещается в стек, 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
    }
Franjo
источник
3

В моей деятельности 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;
    }
Куан Нгуен
источник
у меня не работает
Амир Дора.
2

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

О чем нужно помнить. 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);
}
А.А.верин
источник
0

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

iamlukeyb
источник
1
Не могли бы вы опубликовать пример, пожалуйста?
Кави 06
0

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);
        }
...
Рошан Поудьял
источник
0
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, чтобы увидеть эффект

Луу Куок Тханг
источник
0

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

@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();
}
Махендран Кэнди
источник
0

Я использовал 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;
}
Али
источник