Переключение между изображением ящика навигации Android и курсором вверх при использовании фрагментов

При использовании навигационного ящика разработчики Android рекомендуют, чтобы в ActionBar «только те экраны, которые представлены в навигационном ящике, действительно имели изображение навигационного ящика» и что «все остальные экраны имеют традиционный верхний карат».

Подробнее см. Здесь: http://youtu.be/F5COhlbpIbY

Я использую одно действие для управления несколькими уровнями фрагментов и могу заставить изображение панели навигации отображаться и работать на всех уровнях.

При создании фрагментов нижнего уровня я могу вызвать ActionBarDrawerToggle setDrawerIndicatorEnabled(false), чтобы скрыть изображение панели навигации и отобразить курсор вверх

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout, 
lowFrag, "lowerFrag").addToBackStack(null).commit();

Проблема, с которой я сталкиваюсь, заключается в том, что когда я возвращаюсь к фрагментам верхнего уровня, Up carat все еще показывает вместо исходного изображения ящика навигации. Есть ли предложения о том, как «обновить» ActionBar на фрагментах верхнего уровня, чтобы повторно отобразить изображение панели навигации?


Решение

Предложение Тома сработало для меня. Вот что я сделал:

Основная деятельность

Это действие контролирует все фрагменты в приложении.

При подготовке новых фрагментов для замены других я устанавливаю DrawerToggle setDrawerIndicatorEnabled(false) следующим образом:

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout,   
lowFrag).addToBackStack(null).commit();

Затем, заменив onBackPressed, я вернул описанное выше, установив для DrawerToggle значение setDrawerIndicatorEnabled(true) следующим образом:

@Override
public void onBackPressed() {
    super.onBackPressed();
    // turn on the Navigation Drawer image; 
    // this is called in the LowerLevelFragments
    setDrawerIndicatorEnabled(true)
}

Фрагменты нижнего уровня

Во фрагментах я модифицировал onCreate и onOptionsItemSelected следующим образом:

В onCreate добавлено setHasOptionsMenu(true), чтобы разрешить настройку меню опций. Также установите setDisplayHomeAsUpEnabled(true), чтобы включить на панели действий:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // needed to indicate that the fragment would 
    // like to add items to the Options Menu        
    setHasOptionsMenu(true);    
    // update the actionbar to show the up carat/affordance 
    getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}

Затем в onOptionsItemSelected всякий раз, когда нажимается , он вызывает onBackPressed() из действия для перехода на один уровень вверх в иерархии и отображения изображения ящика навигации:

@Override
public boolean onOptionsItemSelected(MenuItem item) {   
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            getActivity().onBackPressed();
            return true;
        … 
    }

person EvilAsh    schedule 23.06.2013    source источник
comment
Также в вашем методе onBackPressed () вы можете проверить количество записей в заднем стеке с помощью метода getFragmentManager (). GetBackStackEntryCount () и включить индикатор ящика, только если результат равен 0. В этом случае нет необходимости включать homeAsUpIndicator в каждом LowerLevelFragments.   -  person pvshnik    schedule 30.08.2013
comment
Это очень полезно! Вам следует переместить часть своего сообщения, посвященную решению, и сделать ее реальным ответом. Вы получите больше баллов за голоса, и это, в конце концов, ответ   -  person Oleksiy    schedule 15.05.2014
comment
Почему вы заменяете здесь фрагмент: .replace(R.id.frag_layout. Если это еще один уровень иерархии, я ожидаю, что вы .add перейдете на задний план.   -  person JJD    schedule 27.05.2014
comment
Люблю тебя за этот вопрос ‹3   -  person Broak    schedule 14.09.2014
comment
Братан, как ты ссылаешься на theDrawerToggle.setDrawerIndicatorEnabled(false); внутри фрагмента? Я думаю, что это объявлено в файле класса Main Activity. Я не могу найти способ сослаться на это. Есть подсказки?   -  person Skynet    schedule 30.10.2014
comment
При использовании панели инструментов мне пришлось переключить параметры отображения, чтобы в это время не использовать домашний экран. В противном случае метод setDisplayOptions() внутри ToolbarWidgetWrapper (внутреннего пакета android.support.v7.internal.widget) не воссоздает значок при повторном вводе того же фрагмента. Просто оставьте это здесь, когда другие тоже наткнутся на эту проблему.   -  person Wolfram Rittmeyer    schedule 03.03.2015
comment
@Wolfram Rittmeyer, не могли бы вы подробно объяснить, что вы сделали для решения проблемы при использовании панели инструментов?   -  person Sanif SS    schedule 31.08.2016


Ответы (12)


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

Я бы подумал, что тогда вам придется реализовать обратную функциональность вручную: когда пользователь нажимает обратно, у вас есть код, который выталкивает стек (например, в переопределении Activity::onBackPressed). Итак, где бы вы это ни делали, вы можете полностью изменить setDrawerIndicatorEnabled.

person Tom    schedule 29.06.2013
comment
Спасибо, Том, это сработало! Я обновил исходный пост с помощью решения, которое использовал. - person EvilAsh; 30.06.2013
comment
Как ссылаться на theDrawerToggle во фрагменте нижнего уровня? Это было определено в Основном действии, не могу понять это! - person Skynet; 30.10.2014
comment
Вы можете получить активность из фрагмента. Так что просто добавьте получатель в свое основное действие, чтобы получить доступ к переключателю ящика. - person Raphael Royer-Rivard; 28.06.2015

Это просто как 1-2-3.

Если вы хотите добиться:

1) Индикатор ящика - когда в задней стопке нет фрагментов или ящик открыт.

2) Стрелка - когда некоторые фрагменты находятся в задней стопке.

private FragmentManager.OnBackStackChangedListener
        mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        syncActionBarArrowState();
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    getSupportActionBar().setDisplayShowHomeEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    mDrawerToggle = new ActionBarDrawerToggle(
            this,             
            mDrawerLayout,  
            R.drawable.ic_navigation_drawer, 
            0, 
            0  
    ) {

        public void onDrawerClosed(View view) {
            syncActionBarArrowState();
        }

        public void onDrawerOpened(View drawerView) {
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
    getSupportFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}

@Override
protected void onDestroy() {
    getSupportFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
    super.onDestroy();
}

private void syncActionBarArrowState() {
    int backStackEntryCount = 
        getSupportFragmentManager().getBackStackEntryCount();
    mDrawerToggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

3) Оба индикатора должны действовать согласно своей форме.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (mDrawerToggle.isDrawerIndicatorEnabled() && 
        mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    } else if (item.getItemId() == android.R.id.home && 
               getSupportFragmentManager().popBackStackImmediate()) {
        return true;
    } else {
        return super.onOptionsItemSelected(item);
    }
}

P.S. См. Создание панели навигации для разработчиков Android с другими советами о поведении трехстрочного индикатора. .

person riwnodennyk    schedule 13.11.2013
comment
Я остановился на чем-то похожем на твоё. Я думаю, что это решение (использование BackStackChangedListener для переключения между тем, что показано) является наиболее элегантным. Однако у меня есть два изменения: 1) я не вызываю / не меняю индикатор ящика в вызовах onDrawerClosed / onDrawerOpened - это не нужно, и 2) я позволяю самим фрагментам обрабатывать навигацию вверх, создавая AbstractFragment, который они все наследуют, который реализует onOptionsItemSelected (..) и всегда вызывает setHasOptionsMenu (true); - person Espen Riskedal; 25.11.2013
comment
Использование BackStackChangedListener сработало для меня лучше всего. - person Marcel Bro; 04.03.2014
comment
Yout также должен заблокировать жест смахивания для панели навигации в режиме стрелки: mDrawerLayout.setDrawerLockMode (DrawerLayout.LOCK_MODE_LOCKED_CLOSED); и снова включите его в режиме индикатора ящика - person artkoenig; 11.06.2014
comment
@ArtJom - плохая идея. Поэтому индикатор ящика изменен в onDrawerClosed / onDrawerOpened. - person frostymarvelous; 24.06.2014
comment
@EspenRiskedal см. Ответ на комментарий ArtJom выше - person frostymarvelous; 24.06.2014
comment
В итоге я сделал все это во фрагменте NavigationDrawer. Кстати, работает как шарм, спасибо. - person frostymarvelous; 24.06.2014
comment
У этого бетонного раствора есть недостаток. Он не заботится о порядке фрагментов в заднем стеке: в случае, если есть единственный основной фрагмент, все в порядке, однако у вас может быть несколько основных фрагментов (которые перемещаются из nav.drawer). Итак, вы должны позаботиться об этом, например, очистить backstack при нажатии элемента nav.drawer - person Dmitry Gryazin; 31.07.2014
comment
Спасибо за ответ. Мне нужно изменить значки панели действий в innerFragment. когда я вызываю setHasOptionMenu (true) из innerFragment, получаю NullPointerException в onPrepareOptionMenu. @riwnodennyk - person Divya; 07.08.2014
comment
Спасибо за ответ. Работает отлично. Как заменить этот значок стрелки на собственное изображение. @ Iwnodennyk - person Divya; 12.09.2014
comment
@Bagzerg, разве это не (очистить стопку при щелчке по элементу из ящика), о чем говорится в руководстве по дизайну? Я думаю, что да. - person Sufian; 03.10.2014
comment
Я три дня бился головой о стену, а потом приземлился здесь. - person Skynet; 30.10.2014
comment
setActionBarArrowDependingOnFragmentsBackStack() ... какое длинное имя: P - person S.Thiongane; 22.12.2014
comment
Это то, что у меня сработало, но я сделал небольшую настройку, чтобы улучшить его, о чем здесь упоминается stackoverflow.com/a/29025687/1118886 < / а> - person Sheraz Ahmad Khilji; 13.03.2015
comment
У меня не отображается кнопка вверх, когда я перехожу от фрагмента A к B и добавляю A в backstack. :( - person aandis; 14.06.2015
comment
@zack для меня вышеуказанного решения было недостаточно. Чтобы появилась кнопка, я использую это странное (но работающее) решение: stackoverflow.com/a/29594947/1223140. Позже стрелку вверх нельзя было щелкнуть, поэтому я последовал этому решению: stackoverflow.com/a/33872425/1223140 и, наконец, получил это работает. - person Dale Cooper; 25.03.2016
comment
Спасибо. Пожалуйста, перепишите наOptionsItemSelected. Обычно он содержит переключатель для идентификатора элемента. В вашем случае он содержит только один элемент (android.R.id.home) и popBackStackImmediate (). Это не очень хорошая ситуация. Например, я заменил эту логику на onBackPressed (), где сохранены некоторые новые условия. - person CoolMind; 02.08.2016

Я использовал следующее:

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                if(getSupportFragmentManager().getBackStackEntryCount() > 0){
                    mDrawerToggle.setDrawerIndicatorEnabled(false);
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                }
                else {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    mDrawerToggle.setDrawerIndicatorEnabled(true);
                }
            }
        });
person Yuriy Sych    schedule 23.11.2015
comment
БОЛЬШОЕ СПАСИБО, это единственное, что действительно сработало. Как только я добавлю это, drawerToggle.setToolbarNavigationClickListener( этот слушатель вызывается при нажатии стрелки - person EpicPandaForce; 26.01.2017
comment
Спасибо @Yuriy, это помогло мне решить мою проблему - person Muhammad Shoaib Murtaza; 21.03.2020

Если кнопка на панели действий вверх не работает, не забудьте добавить слушателя:

// Navigation back icon listener
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
});

У меня возникли проблемы с реализацией навигации по ящику с помощью кнопки «Домой», все работало, кроме кнопки действия.

person Burrich    schedule 14.01.2016

Попробуйте обработать выбор элемента Home в MainActivity в зависимости от состояния DrawerToggle. Таким образом, вам не нужно добавлять один и тот же код к каждому фрагменту.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            onBackPressed();
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}
person dzeikei    schedule 02.08.2013
comment
+1 аккуратное решение. Чтобы ваш ответ был полностью пригоден для использования, вы должны добавить чек в backstack. Когда он пуст, автоматически установите индикатор ящика на истинное значение. - person HpTerm; 02.10.2013
comment
@HpTerm Я обрабатываю backstack в onBackPressed(), потому что мне нужно одинаковое поведение для обоих. - person dzeikei; 03.10.2013

ПОСЛЕДУЮЩИЕ ДЕЙСТВИЯ

Решение, данное @dzeikei, является изящным, но его можно расширить при использовании фрагментов, чтобы автоматически обрабатывать возврат индикатора ящика, когда backstack пуст.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            // Use getSupportFragmentManager() to support older devices
            FragmentManager fragmentManager = getFragmentManager();
            fragmentManager.popBackStack();
            // Make sure transactions are finished before reading backstack count
            fragmentManager.executePendingTransactions();
            if (fragmentManager.getBackStackEntryCount() < 1){
                mDrawerToggle.setDrawerIndicatorEnabled(true);  
            }
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

ИЗМЕНИТЬ

На вопрос @JJD.

Фрагменты удерживаются / управляются в деятельности. Приведенный выше код записывается один раз в этом действии, но обрабатывает только верхний курсор для onOptionsItemSelected.

В одном из моих приложений мне также нужно было обрабатывать поведение курсора вверх при нажатии кнопки возврата. Это можно исправить, переопределив onBackPressed.

@Override
public void onBackPressed() {
    // Use getSupportFragmentManager() to support older devices
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.executePendingTransactions();
    if (fragmentManager.getBackStackEntryCount() < 1){
        super.onBackPressed();
    } else {
        fragmentManager.executePendingTransactions();
        fragmentManager.popBackStack();
        fragmentManager.executePendingTransactions();
        if (fragmentManager.getBackStackEntryCount() < 1){
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    }
};

Обратите внимание на дублирование кода между onOptionsItemSelected и onBackPressed, которого можно избежать, создав метод и вызвав этот метод в обоих местах.

Также обратите внимание, что я добавляю еще два раза executePendingTransactions, которые в моем случае требовались, иначе у меня иногда было странное поведение курсора вверх.

person HpTerm    schedule 02.10.2013
comment
Это полный код, который мне нужно добавить для реализации поведения курсора Вверх, или вы ссылаетесь на одно из других сообщений? Пожалуйста, поясните это. - person JJD; 27.05.2014
comment
Спасибо за редактирование. На самом деле я поддерживаю mDrawerToggle в NavigationDrawerFragment классе. Чтобы он заработал, мне также нужно переключить состояние кнопки / индикатора «Домой» - см .: NavigationDrawerFragment#toggleDrawerIndicator. Кроме того, я не уверен, что вам нужна начальная проверка в onOptionsItemSelected: я его раскомментировал. - Упрощенный пример - person JJD; 28.05.2014
comment
Исправленная реализация: насчет первоначальной проверки onOptionsItemSelected вы правы. Это гарантирует, что панель навигации по-прежнему будет открываться в иерархии верхнего уровня. Однако я переместил код в NavigationDrawerFragment#onOptionsItemSelected. Это помогает мне не подвергать mDrawerToggle воздействию MainActivity. - person JJD; 28.05.2014
comment
@JJD Я вспомнил, как я немного боролся с тем, чтобы все работало для восходящего курсора для каждого уровня иерархии. Пока это работает и для вас, это нормально. Конечно, как вы говорите, вы можете переместить код в другое место, чтобы не раскрывать его. - person HpTerm; 28.05.2014

Я создал интерфейс для хостинга, чтобы обновить состояние просмотра гамбургер-меню. Для фрагментов верхнего уровня я установил переключатель в положение true, а для фрагментов, для которых я хочу отображать стрелку вверх ‹, я установил переключатель в положение false.

public class SomeFragment extends Fragment {

    public interface OnFragmentInteractionListener {
        public void showDrawerToggle(boolean showDrawerToggle);
    }

    private OnFragmentInteractionListener mListener;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            this.mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mListener.showDrawerToggle(false);
    }
}

Тогда в моей деятельности ...

public class MainActivity extends Activity implements SomeFragment.OnFragmentInteractionListener {

    private ActionBarDrawerToggle mDrawerToggle;

    public void showDrawerToggle(boolean showDrawerIndicator) {
        mDrawerToggle.setDrawerIndicatorEnabled(showDrawerIndicator);
    }

}
person Bill Mote    schedule 05.09.2014

Этот ответ работал, но с ним возникла небольшая проблема. getSupportActionBar().setDisplayHomeAsUpEnabled(false) не вызывался явным образом, и из-за этого значок ящика был скрыт, даже когда в заднем стеке не было элементов, поэтому изменение метода setActionBarArrowDependingOnFragmentsBackStack() сработало для меня.

private void setActionBarArrowDependingOnFragmentsBackStack() {
        int backStackEntryCount = getSupportFragmentManager()
                .getBackStackEntryCount();
        // If there are no items in the back stack
        if (backStackEntryCount == 0) {
            // Please make sure that UP CARAT is Hidden otherwise Drawer icon
            // wont display
            getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            // Display the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        } else {
            // Show the Up carat
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            // Hide the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(false);
        }

    }
person Sheraz Ahmad Khilji    schedule 13.03.2015
comment
в моем случае правильное решение использовало только actionBarDrawerToggle.setDrawerIndicatorEnabled (getSupportFragmentManager (). getBackStackEntryCount () ‹0); - person Gorets; 07.12.2016

Логика понятная. Показывать кнопку возврата, если стек фрагментов пустой. Показывать анимацию возврата гамбургера материала, если стопка фрагментов непонятна.

getSupportFragmentManager().addOnBackStackChangedListener(
    new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            syncActionBarArrowState();
        }
    }
);


private void syncActionBarArrowState() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    mNavigationDrawerFragment.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

//add these in Your NavigationDrawer fragment class

public void setDrawerIndicatorEnabled(boolean flag){
    ActionBar actionBar = getActionBar();
    if (!flag) {
        mDrawerToggle.setDrawerIndicatorEnabled(false);
        actionBar.setDisplayHomeAsUpEnabled(true);
        mDrawerToggle.setHomeAsUpIndicator(getColoredArrow());
    } else {
        mDrawerToggle.setDrawerIndicatorEnabled(true);
    }
    mDrawerToggle.syncState();
    getActivity().supportInvalidateOptionsMenu();
}

//download back button from this(https://www.google.com/design/icons/) website and add to your project

private Drawable getColoredArrow() {
    Drawable arrowDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_arrow_back_black_24dp);
    Drawable wrapped = DrawableCompat.wrap(arrowDrawable);

    if (arrowDrawable != null && wrapped != null) {
        // This should avoid tinting all the arrows
        arrowDrawable.mutate();
        DrawableCompat.setTint(wrapped, Color.GRAY);
    }
    return wrapped;
}
person kml_ckr    schedule 07.09.2015

Если вы взглянете на приложение GMAIL и зайдете сюда, чтобы найти значок каретки / аффорданса ..

Я прошу вас сделать это, ни один из приведенных выше ответов не был ясен. я смог изменить принятый ответ.

  • NavigationDrawer -> Listview содержит подфрагменты.


  • субфрагменты будут перечислены следующим образом

  • firstFragment == позиция 0 ---> у этого будут подфрагменты -> фрагмент

  • secondFragment
  • thirdFragment и так далее ....

В firstFragment у вас есть другой фрагмент.

Назовите это DrawerActivity

getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getFragmentManager().getBackStackEntryCount() > 0) {
                mDrawerToggle.setDrawerIndicatorEnabled(false);
            } else {
                mDrawerToggle.setDrawerIndicatorEnabled(true);
            }
        }
    });

и во фрагменте

    setHasOptionsMenu(true);    

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            activity.onBackPressed();
            return true;
    }
    return false;
}

В методе действия OnBackPressed Drawer установите переключатель ящика в положение true, чтобы снова включить значок списка навигации.

Спасибо, Pusp

person EngineSense    schedule 07.10.2015

Вы можете посмотреть на этот небольшой пример! https://github.com/oskarko/NavDrawerExample

person oskarko    schedule 26.09.2016

IMO, использование onNavigateUp () (как показано здесь) в решении riwnodennyk или Tom чище и, кажется, работает лучше. Просто замените код onOptionsItemSelected следующим:

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
        // handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
person 0101100101    schedule 15.09.2014