#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()
Комментариев нет:
Отправить комментарий