Фрагменты на выходе из заднего стека
Я использую пакет совместимости для использования фрагментов с Android 2.2. При использовании фрагментов и добавлении переходов между ними в backstack я хотел бы добиться того же поведения onResume действия, т. Е. Всякий раз, когда фрагмент выводится на "передний план" (видимый для пользователя) после всплывающего из backstack, я бы хотел, чтобы внутри фрагмента была активирована какая-то функция обратного вызова (например, для выполнения определенных изменений в общем ресурсе пользовательского интерфейса).
Я видел, что в рамках фрагмента нет встроенного обратного вызова. Есть ли хорошая практика для достижения этой цели?
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()
будет вызван после нажатия "Назад". несколько предостережений, хотя:
- Предполагается, что вы добавили все транзакции в backstack (используя
FragmentTransaction.addToBackStack()
) - Он будет активирован при каждом изменении стека (вы можете хранить другие вещи в заднем стеке, такие как анимация), чтобы вы могли получить несколько вызовов для одного и того же экземпляра фрагмента.
Я немного изменил предложенное решение. У меня лучше работает вот так:
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;
}
После popStackBack()
Вы можете использовать следующий обратный вызов: onHiddenChanged(boolean hidden)
внутри вашего фрагмента
Если фрагмент помещается в 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
}
В следующем разделе "Разработчики Android" описывается механизм взаимодействия Создание обратных вызовов событий для действия. Чтобы процитировать строку из этого:
Хороший способ сделать это - определить интерфейс обратного вызова внутри фрагмента и потребовать, чтобы действия хоста его реализовали. Когда действие получает обратный вызов через интерфейс, оно может обмениваться информацией с другими фрагментами в макете по мере необходимости.
Изменить: фрагмент имеет onStart(...)
который вызывается, когда фрагмент виден пользователю. Точно так же onResume(...)
когда видно и активно работает. Они связаны с их деятельностью коллег. Короче говоря: использовать onResume()
В моей деятельности 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;
}
Немного улучшен и обернут в решение менеджера.
Вещи, чтобы иметь в виду. 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);
}
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);
}
...
Это правильный ответ, который вы можете вызвать onResume(), если фрагмент прикреплен к действию. В качестве альтернативы вы можете использовать onAttach и onDetach
Мой обходной путь - получить текущий заголовок панели действий во Фрагменте, прежде чем установить новый заголовок. Таким образом, после извлечения фрагмента я могу вернуться к этому названию.
@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();
}
Я использовал 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;
}
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, чтобы увидеть эффект