Фрагменты на выходе из заднего стека - PullRequest
91 голосов
/ 28 июня 2011

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

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

Ответы [ 12 ]

110 голосов
/ 28 июня 2011

Из-за отсутствия лучшего решения я получил следующее: Предположим, у меня есть 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. Он будет активирован при каждом стеке изменить (вы можете хранить другие вещи в задний стек, такой как анимация) так Вы можете получить несколько звонков для тот же экземпляр фрагмента.
32 голосов
/ 12 сентября 2013

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

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;
}
5 голосов
/ 16 апреля 2015

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

3 голосов
/ 28 июня 2011

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

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

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

2 голосов
/ 17 марта 2016

В моей деятельности 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 голосов
/ 22 сентября 2015

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

Что нужно иметь в виду. 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);
}
1 голос
/ 01 ноября 2014

Если фрагмент помещается в backstack, 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
    }
0 голосов
/ 20 января 2017

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

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

pass 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;
}
0 голосов
/ 19 мая 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();
}
0 голосов
/ 24 марта 2016
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();
 }
}

и окончательный экстент вашего Frament из RootFragment, чтобы увидеть эффект

...