Берем пример по ссылке – работает отлично. Добавляем одну ма-а-аленькую строчку:

fragmentTransaction. addToBackStack(null);

и тут же выясняется, что заголовок в ActionBar при возврате не обновляется, равно как и позиция в самом Navigation Drawer… Казалось бы – можно поручить обновление заголовка самому фрагменту, но, во-первых, это не тривиально, т.к. из FragmentActivity нет прямого доступа к getSupportActionBar(), а, во-вторых, надо же еще как-то Navigation Drawer извещать о том, что подсветить следует совсем другой пункт списка. А какой?



Мое решение ниже.

Чтобы получить из фрагмента доступ к родному ActionBar и Navigation Drawer, логичнее всего использовать слушатель для связи с Activity, которая содержит фрагмент. Это решение, кстати, и сам Гугл предлагает для такого взаимодействия.

Все замечательно, заголовок в Activity передать легко, но каким образом фрагмент может сообщить Navigation Drawer о том, какой именно пункт списка выделить? Отправить номер своей позиции, о которой он, типа, откуда-то знает?

Усложним задачу – есть несколько Activity, которые могут использовать одни и те же фрагменты, причем, в разных позициях Navigation Drawer. Тут уже прямой отправкой номера позиции из фрагмента не обойдешься. И хорошо, ибо это уже костылище, в отличие от отправки заголовка, который – так, костыленок.

В общем, прописываем куда-нибудь константы с типами всех фрагментов (у меня они уже были в фабрике фрагментов):

public static final int FRAGMENT_MAIN_CALENDAR = 1;
public static final int FRAGMENT_MAIN_CATALOG = 2;
public static final int FRAGMENT_MAIN_FAVORITES = 3;
public static final int FRAGMENT_AUTHOR_ABOUT = 4;
public static final int FRAGMENT_AUTHOR_STORIES = 5;

и передаем их в Activity, которая сама уж разберется, к какой позиции Navigation Drawer сейчас этот фрагмент приписан.

А заголовок мне все равно пришлось отправлять, а не генерировать в Activity, т.к. порой он привязан к содержимому фрагмента (например, ФИО автора или название открытого рассказа), а генерить лишний запрос к базе не хочется.
В общем, вот такой интерфейс:

public interface OnFragmentChangedListener {
	public void onFragmentChanged(String title, int fragmentType);
}

Его имплементит Activity и реализует метод:

@Override
public void onFragmentChanged(String title, int fragmentType) {
	switch (fragmentType) {
	case FragmentsFabrica.FRAGMENT_MAIN_CALENDAR : mDrawerList.setItemChecked(0, true);
		break;
	case FragmentsFabrica.FRAGMENT_MAIN_CATALOG : mDrawerList.setItemChecked(1, true);
		break;
	case FragmentsFabrica.FRAGMENT_MAIN_FAVORITES : mDrawerList.setItemChecked(2, true);
		break;
	}
	setTitle(title);
}

где setTitle – из гугловского примера.
Сами фрагменты в onAttach регистрируют Activity как слушатель и вызывают его в onCreateView или в onActivityCreated с его, фрагмента, индивидуальными заголовком и типом:

onFragmentChangedListener.onFragmentChanged(
	getResources().getString(R.string.navigation_main_section1_title), 
	FragmentsFabrica.FRAGMENT_MAIN_CALENDAR);

Но одну проблему мне так и не удалось решить. Почему-то, когда отлистываешь фрагменты назад до конца, Activity на закуску показывает пустой экран и закрывается только по еще одному нажатию кнопки Back. При этом на этом пустом экране стек фрагментов нулевой длины – с фига бы не закрыться? Пришлось добавить в Activity костыль:

@Override
public void onBackPressed() {
	super.onBackPressed();
    	FragmentManager fm = getSupportFragmentManager();
    	if (fm.getBackStackEntryCount() == 0)
    		this.finish();
}

С чем это связано, и как грамотно решить проблему – пока непонятно.

Комментарии (17)


  1. serso
    06.05.2015 11:50
    +7

    Если вы добавили 3 фрагмента в back stack, то вам нужно будет нажать 3 раза кнопку back, чтобы их оттуда убрать. Четвёртое нажатие закроет activity, т.к. back stack будет пустым. Чтобы правильно решить проблему создавайте первый фрагмент без добавления в back stack.


    1. Oksumoron Автор
      06.05.2015 11:53

      О! Спасибо! Попробую.


  1. Bolloky
    06.05.2015 14:08

    В качестве айдишников фрагмента прекрасно подходят строковые ресурсы.

    @Override
    public void onFragmentChanged(int id) {
        switch (fragmentType) {
        case R.string.title1: mDrawerList.setItemChecked(0, true);
            break;
        case R.string.title2: mDrawerList.setItemChecked(1, true);
            break;
        }
        setTitle(id);
    }
    


    1. Oksumoron Автор
      06.05.2015 14:43

      При разделении айдишника и заголовка появляется возможность генерить заголовки динамически в зависимости от содержания фрагмента, что мне важно. Как я писала — то же ФИО автора или заголовок текущего рассказа.


  1. mairos
    06.05.2015 16:59

    Вы предлагаете по кнопке Back открывать предыдущий экран верхнего уровня Navigation Drawer? Это очень похоже на «анти-паттерн навигации в Android № 6». Гайдлайны Google это явным образом не рекомендуют (System Back at the top level of the app).


    1. Oksumoron Автор
      06.05.2015 18:43

      Читайте внимательнее. Я предлагаю по кнопке Back открывать предыдущий открытый пользователем фрагмент, как это рекомендует гугл. При таком возврате в Экшнбаре и в самом навигаторе информация должна обновиться, но сама она не обновляется — об этом приходится заботиться разработчику.


  1. Zeliret
    07.05.2015 11:56

    Казалось бы – можно поручить обновление заголовка самому фрагменту, но, во-первых, это не тривиально, т.к. из FragmentActivity нет прямого доступа к getSupportActionBar()
    Не совсем понял. Из фрагмента есть же доступ к ActionBar через хост-активити.


    1. Oksumoron Автор
      07.05.2015 12:00

      getActivity() возвращает FragmentActivity, у которой доступа нет. Делать насильственное преобразование типов — плохо. Лучше через слушатели связку реализовать.


      1. withoutuniverse
        07.05.2015 13:08

        Что за глупости. Если я наверняка знаю, что все мои фрагменты лежат в BaseActivity, то всегда могу вызвать ее метод.

        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            mActivityContract = (ActivityContract) activity;
        }

        Ваши слова себе же противоречат:
        Сами фрагменты в onAttach регистрируют Activity как слушатель
        Не понимаю, как будет реализована связка через слушателей без того же преобразования типов — приведите пример, пожалуйста.

        Соглашусь в том, что Activity должен руководить изменениями в ActionBar, а не фрагмент, так как в таком случае проще будет добавлять поддержку планшетов и прочих модернизаций, касающихся функционала.


        1. Oksumoron Автор
          07.05.2015 13:14

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


          1. withoutuniverse
            07.05.2015 14:34

            Теперь я вас понял.
            Поддержу вас в том, что для расширяемости приложения удобнее логику работы с ActionBar хранить в Activity (ну если только архитектура это позволяет, ведь какой-нибудь ToolBar может быть прямо внутри фрагмента, и стучать колбэками из Activity в него плохой стиль, имхо).
            И все же не вижу существенной разницы в приведении типа между Object и IObject. Можете обосновать этот момент — почему это вам не нравится?


            1. Oksumoron Автор
              07.05.2015 15:13

              По поводу интерфейсов я согласна с одним из основных принципов проектирования: «Программируйте на уровне интерфейсов, а не на уровне реализации». Такой подход обеспечивает гораздо большую гибкость в очень многих случаях. Конечно, далеко не везде его имеет смысл применять — например, для крошечных задач создавать отдельный интерфейс нелепо. Но для подобных — вполне оправданно.

              Возвращаясь к данному частному случаю — а захотите вы добавить еще одну активити с подобным же функционалом. Вот у меня их уже две. Чтобы реализовать преобразование типов на уровне объектов, нужен будет общий предок и изменение кода, завязанного на ранее использованный класс. В случае использования интерфейсов в уже написанный код лезть не придется.


              1. andkulikov
                09.05.2015 11:06

                а можно еще и от интерфейсов и приведения к ним отказаться и перейти на логику ивентов с помощью либы
                github.com/greenrobot/EventBus
                или подобной. активити регистрируется как слушатель ивента смены фрагмента. а фрагмент посылает эти ивенты, в которые вкладывает нужную информацию с айдишником и текстом для экшнбара. так еще гибче получается


                1. Oksumoron Автор
                  09.05.2015 11:22

                  Может быть. Но стоит и соблюдать баланс между гибкостью и объемом кода. Если задачу можно решить несколькими строками — зачем цеплять либу?

                  Но себе запомнила, спасибо. Вдруг, когда пригодится :)


                  1. andkulikov
                    09.05.2015 12:02

                    хотя бы потому что это лишь одна из задач, которые можно решить этим подходом, вариантов на большой проект кучи) и либа сама по себе очень легкая и компактная по объему кода. еще можно использовать для тех же целей LocalBroadcastReceiver из support библиотеки, но он не такой удобный.


                    1. Oksumoron Автор
                      09.05.2015 12:42

                      Если в приложении есть куча задач, которые требуют такого решения — да, обосновано. Если нет — я предпочитаю более легкие решения. Мне пока Броадкаст ресивер и иже с ним без надобности, поэтому обхожусь слушателями для связки активити с фрагментами. Но на заметку взяла — не последнее приложение, надеюсь, пишу.


                1. danikula
                  13.05.2015 17:32

                  «Большая сила порождает большую ответственность» (с): )
                  EventBus и им подобные решения — очень мощные, но опасные, т.к. уменьшают очевидность хода выполнения приложения.