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


94

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

Я побачив, що в межах фрагмента немає вбудованого зворотного дзвінка. чи є добра практика для досягнення цього?


13
Чудове запитання. Я намагаюся змінити заголовок панелі дій залежно від того, який фрагмент видно у випадку використання для цього сценарію, і мені здається, що це відсутній зворотній зв'язок в API.
Манфред Мозер

3
У вашій діяльності можна встановити заголовок, враховуючи, що він знає, які фрагменти відображаються в будь-який момент. Також я гадаю, що ви могли б зробити щось, у onCreateViewчому за попсом викличуть фрагмент.
PJL

1
@PJL +1 Відповідно до посилання, це так, як це слід зробити. Однак я віддаю перевагу слухачеві
momo

Відповіді:


115

Через відсутність кращого рішення я отримав це для мене: припустимо, у мене є 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. Він передбачає, що ви додали всі транзакції до рюкзака (використовуючи FragmentTransaction.addToBackStack())
  2. Він буде активований при кожній зміні стека (ви можете зберігати інші речі в задній стек, такі як анімація), щоб ви могли отримати кілька дзвінків за один і той же екземпляр фрагмента.

3
Ви повинні прийняти власну відповідь як правильну, якщо вона працювала на вас.
powerj1984

7
Як це працює з точки зору ідентифікатора фрагмента, який ви запитуєте? Чим він відрізняється для кожного фрагмента, а правильний - у задній стеці?
Манфред Мозер

2
@Warpzit цей метод не викликається, і андроїд-документи спокійно визнають, що його ніколи не буде викликано (він називається лише тоді, коли активність відновиться - чого ніколи, якщо ця діяльність вже активна)
Адам

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

1
поясніть, будь ласка, різницю, і навіщо це використовувати.
Math chiller

2
Різниця між моїм рішенням і попереднім полягає в тому, що при кожній зміні фрагмента, як-от додавання, заміна або повернення назад в купі фрагментів, буде називатися метод "onResume" верхнього фрагмента. У цьому методі немає жодного фрагмента ідентифікатора. В основному він викликає onResume () у фрагменті, який ви переглядаєте при кожній зміні, незалежно від того, завантажено воно в пам'ять чи ні.
Brotoo25

9
Немає записів для методу, який називається getFragments()SupportFragmentManager ... -1: - /
Protostome

1
OnResume () називається. Але я отримую порожній екран. Чи є інший спосіб вирішити цю проблему?
kavie

Я думаю, що це призводить до винятку ArrayOutOfBounds, якщо backStackEntryCount дорівнює 0. Чи помиляюся? Спробував редагувати публікацію, але її відхилили.
Майк Т

6

Після a popStackBack()ви можете використовувати наступний зворотний дзвінок: onHiddenChanged(boolean hidden)у межах вашого фрагмента


2
Схоже, це не працює з фрагментами компат програми.
Ло-Тан

Ось чим я користувався. Дякую!
Альберт Віла Кальво

Найпростіше рішення! Просто додайте галочку, якщо фрагмент видно в даний час і вуаля, ви можете встановити назву панелі додатків.
EricH206

4

У наступному розділі для розробників Android описаний механізм зв’язку Створення зворотних викликів подій до активності . Процитуйте рядок із нього:

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

Редагувати: фрагмент має onStart(...)виклик, коли фрагмент видно користувачеві. Точно так само, onResume(...)коли видно і активно працює. Вони пов'язані зі своїми колегами по активності. Коротше кажучи: використанняonResume()


1
Я просто шукаю метод у класі Fragment, який активується кожного разу, коли його показують користувачеві. чи через FragmentTransaction.add () / substitution (), чи FragmentManager.popBackStack ().
oriharel

@oriharel Дивіться оновлену відповідь. Коли діяльність завантажується, ви отримуватимете різні дзвінки: onAttach, onCreate, onActivityCreate, onStart, onResume. Якщо ви викликали `setRetainInstance (true) ', тоді ви не отримаєте onCreate, коли фрагмент повторно відкриється при натисканні назад.
PJL

15
onStart () ніколи не викликається при натисканні кнопки "Назад" між фрагментами в тій самій діяльності. Я спробував більшість зворотних дзвінків у документації, і жоден з них не називається за таким сценарієм. дивіться мою відповідь для вирішення.
oriharel

hmm with compat lib v4 Я бачу onResume для мого фрагмента. Мені подобається відповідь @ oriharel, хоча, як це відрізняє стає видимим через popBackStack, а не просто виведення на перший план.
PJL

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 не є однотонним, він управляє лише фрагментами в межах діяльності, тому в кожній діяльності він буде новим. Крім того, це рішення поки що не враховує 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);
}

1

Якщо фрагмент поміщений у backstack, Android просто знищує його погляд. Сам екземпляр фрагмента не вбивається. Простий спосіб почати - слухати подію onViewCreate, вказуючи логіку "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
    }

0

Це правильна відповідь, яку можна викликати onResume (), якщо фрагмент додається до активності. Крім того, ви можете використовувати onAttach та onDetach


1
Чи можете ви розмістити приклад, будь ласка?
kavie

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