#java #android #fragment_back_stack #fragment_manager #fragment_transaction
Разрабатываю приложение несколько месяцев, оно постепенно обрастает новыми добавлениями.
Вкратце - это интернет магазин, оформлен в виде одной активити с NavigationDrawer,
все страницы реализованы на фрагментах, в некоторых случаях в дело вступает backstack,
например в случае каталога, у которого есть 4 уровня вложенности - основные категории,
подкатегории, список продуктов, страница продукта. первые два оформлены в виде ListView,
третий в виде RecyclerView с GridLayoutManager.
Метод onBackPressed в активити определен следующим образом
@Override
public void onBackPressed() {
ProductPageFragment ppf = (ProductPageFragment) getFragmentManager().findFragmentByTag(Flags.Stack.PRODUCT_PAGE_FRAGMENT);
Log.d("ppf", ppf + "");
SubcatalogFragment sf = (SubcatalogFragment) getFragmentManager().findFragmentByTag(Flags.Stack.SUBCATALOG_FRAGMENT);
Log.d("sf", sf + "");
ProductGridFragment pf = (ProductGridFragment) getFragmentManager().findFragmentByTag(Flags.Stack.PRODUCT_GRID_FRAGMENT);
Log.d("pf", pf + "");
CartMainFragment cmf = (CartMainFragment) getFragmentManager().findFragmentByTag(Flags.Stack.CART_FRAGMENT);
Log.d("cmf", cmf + "");
PurchaseFragment purf = (PurchaseFragment) getFragmentManager().findFragmentByTag(Flags.Stack.PURCHASE_FRAGMENT);
Log.d("purf", purf + "");
boolean sfVisible = sf != null && sf.isVisible();
boolean ppfVisible = ppf != null && ppf.isVisible();
boolean pfVisible = pf != null && pf.isVisible();
boolean cmfVisible = cmf != null && cmf.isVisible();
boolean purfVisible = purf != null && purf.isVisible();
//If the fragment exists and has some back-stack entry
if ((sfVisible || pfVisible || ppfVisible || cmfVisible || purfVisible) && getFragmentManager().getBackStackEntryCount()
> 0) {
bpCount = 0;
getFragmentManager().popBackStack();
}
//Else, nothing in the direct fragment back stack
else {
// Let super handle the back press
bpCount++;
switch (bpCount) {
case 1:
Toast.makeText(
getApplicationContext(),
"Для выхода нажмите \"Назад\" ещё раз",
Toast.LENGTH_SHORT
).show();
break;
case 2:
super.onBackPressed();
break;
}
}
}
bpCount, в данном случае, – счетчик нажатий на back, чтобы приложение не закрывалось
по первому нажатию.
На главной странице потребовалось разместить популярные продукты, использовал для
этого RecyclerView с GridLayoutManager и тем же viewholder, что и в списке продуктов.
По нажатию на итем должна открываться страница продукта. Запускаем приложение - видим
список продуктов, открываем страницу первого попавшегося продукта - отрисовывается
нормально. Нажимаем клавишу back, видим главную, открываем другой продукт (или этот
же, не имеет значения) и фрагмент не отрисовывается. если помещать его в контейнер
методом replace - то видим серый экран (стандартный фон) на месте контейнера, если
add, то RecyclerView отскролливается на выбранный продукт, при этом в обоих случаях
onCreateView фрагмета выполняется.
Код, вызывающий фрагмент выглядит так:
private void showProductPage() {
ProductPageFragment ppf = new ProductPageFragment();
Bundle bundle = new Bundle();
bundle.putParcelable(PRODUCT_TAG, product);
ppf.setArguments(bundle);
((Activity) this.itemView.getContext()).getFragmentManager().beginTransaction()
.replace(R.id.container, ppf, Flags.Stack.PRODUCT_PAGE_FRAGMENT)
.addToBackStack(Flags.Stack.CATALOG_BACKSTACK)
.commit();
}
this - это viewholder, itemView - view холдера, контекст является инстансом активити.
Мне бы хотелось понять, почему такое может происходить, ведь по первому клику фрагмент
отрисовывается нормально. И если кто нибудь подробно может мне объяснить разницу между
основными методами FragmentTransaction, то я тоже был бы очень признателен. Спасибо.
PS. Да, забыл, к чему я, собственно, расписывал как устроен каталог - во всем каталоге
нажатие на back отрабатывает нормально, а вот со страницей продукта происходит такая
же ерунда, как и при открытии его с главной. При первом запуске страница продукта отрисовывается,
а потом - нет.
PPS. Удалил лишние проверки из popBackStack, на всякий случай, ситуация не изменилась.
Вроде всё выполняется в одном потоке, но объявлял syncronized как метод, так и блок,
результат тот же. Мне не понятно, почему метод onCreateView выполняется, как и остальные
методы фрагмента, в методе onResume вывожу isVisible и оно true, но при этом вьющка
не рисуется. Или может она рисуется где то ещё? Смотрим реализацию isVisible в базовом
классе фрагмента:
final public boolean isVisible() {
return isAdded() && !isHidden() && mView != null
&& mView.getWindowToken() != null && mView.getVisibility() == View.VISIBLE;
}
Значит нам надо выяснить, как реализованы и что значат все эти условия. Ищем isAdded
final public boolean isAdded() {
return mActivity != null && mAdded; // Задается FragmentManager при добавлении
фрагмента
}
isHidden:
final public boolean isHidden() {
return mHidden; // значение задается функциями showFragment и hideFragment
вызванные из экземляра FragmentManager
}
mView это view фрагмента, который создаётся в onCreateView,
view.getWindowToken выглядит так
public IBinder getWindowToken() {
return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
// возвращет null если view не присоединено к контейнеру
}
ну и View.VISIBLE означает, что view фрагмента отрисовывается.
Следовательно ОС считает, что фрагмент у меня нарисован и существует.
Ответы
Ответ 1
Не увидел в вашем коде используете ли вы рекомендованный паттерн OnFragmentInteraction - судя по обилию всяких условий в onBackPressed видимо нет, а зря. Смысл паттерна, в том, что корневой Activity должен реализовывать интерфейс отрабатывающий изменения в подчиненных фрагментах. Грубо говоря, если вы выбрали некий элемент списка, то корневой Activity должен быть в курсе этого, чтобы при возврате обратно отработать эти изменения. По-моему это плохая идея ловить onBackPressed - гораздо удобнее работать с onBackStackChanged примерно так: @Override public void onBackStackChanged() { Log.i(TAG, "Back stack changed!"); Log.i(TAG, "Stack size=" + getSupportFragmentManager().getBackStackEntryCount()); if(getSupportFragmentManager().getBackStackEntryCount()==0) {//we're on main //перерисовываем здесь } shouldDisplayHomeUp(); } Для перерисовки надо или дать знать фрагменту, что контент изменился - типа notifyDatasetChanged или просто перерисовать Cursor который лежит под фрагментом (если лежит). Можно попробовать отцепить и заново прицепить фрагмент: getSupportFragmentManager() .beginTransaction() .detach(contentFragment) .attach(contentFragment) .commit(); Говорят работает - честно скажу не пробовал.Ответ 2
Попробуйте в адаптер передать ссылку на фрагмент, коий содержит ваш RecyclerView и вместо FragmentManager-а активити использовать FragmentManager фрагмента, который можно получить методом getChildFragmentManager() класса Fragment. Т.е. вместо ((Activity) this.itemView.getContext()).getFragmentManager() сделать instanceOfYoursFragmentThatHoldesRecycleViewWhichYouPassToAdaptersClass.getChildFragmentManager()
Комментариев нет:
Отправить комментарий