Переключение между изображением Android Navigation Drawer и кареткой Up при использовании фрагментов

177

При использовании Навигационного Ящика разработчики 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();

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


Решение

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

Основное занятие

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

При подготовке новых фрагментов для замены других я установил 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;
         
    }
EvilAsh
источник
2
Также в вашем методе onBackPressed () вы можете проверить, сколько записей находится в заднем стеке, с помощью метода getFragmentManager (). GetBackStackEntryCount () и включить индикатор ящика, только если результат равен 0. В этом случае нет необходимости включать homeAsUpIndicator в каждом LowerLevelFragments.
пвшник
2
Это очень полезно! Вы должны переместить часть «решения» своего поста и сделать его фактическим «ответом». Вы получите больше очков за upvotes и есть ответ после того, как все
Алексей
Почему вы заменить фрагмент здесь: .replace(R.id.frag_layout. Если это еще один уровень иерархии, я бы ожидал, что вы .addвернетесь в прошлое.
JJD
5
Братан, как вы ссылаетесь theDrawerToggle.setDrawerIndicatorEnabled(false);на фрагмент? Я думаю, что это объявлено в файле класса Main Activity. Я не могу найти способ ссылаться на это. Есть намеки?
Skynet,
1
При использовании панели инструментов мне приходилось переключать параметры отображения, чтобы не использовать домашнюю страницу, как в это время. В противном случае setDisplayOptions()метод внутри ToolbarWidgetWrapper(из внутреннего пакета android.support.v7.internal.widget) не воссоздает значок при повторном вводе того же фрагмента. Просто оставьте это здесь, когда другие тоже натолкнутся на эту проблему.
Вольфрам Риттмейер

Ответы:

29

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

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

Том
источник
Спасибо Том, что сработало! Я обновил исходный пост с решением, которое я использовал.
EvilAsh
6
Как сослаться на theDrawerToggle во фрагменте более низкого уровня? Это было определено в Основном упражнении, я не могу понять это!
Скайнет
1
Вы можете получить активность из фрагмента. Так что просто добавьте геттер в вашу основную деятельность, чтобы получить доступ к переключателю ящика.
Рафаэль Ройер-Ривард
83

Это просто как 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);
    }
}

PS См. Раздел « Создание навигационного ящика на Android-разработчике», где приведены другие советы о поведении трехстрочного индикатора.

riwnodennyk
источник
3
Я закончил на что-то похожее на ваше. Я думаю, что это решение (с помощью BackStackChangedListener для переключения между показанным) является наиболее элегантным. Однако у меня есть два изменения: 1) я не вызываю / не изменяю индикатор ящика в вызовах onDrawerClosed / onDrawerOpened - он не нужен, и 2) я позволяю самим фрагментам обрабатывать навигацию Up, создавая AbstractFragment, который они все наследуют, и который реализует onOptionsItemSelected (..) и всегда вызывает setHasOptionsMenu (true);
Эспен Рискедал
2
Yout также должен заблокировать жест смахивания для панели навигации в режиме стрелки: mDrawerLayout.setDrawerLockMode (DrawerLayout.LOCK_MODE_LOCKED_CLOSED); и включите его снова в режиме индикатора ящика
artkoenig
5
Я сделал все это в своем фрагменте NavigationDrawer. Работает как шарм кстати, спасибо.
морозно чудесный
1
setActionBarArrowDependingOnFragmentsBackStack()... какое длинное имя: P
S.Thiongane
2
Это не показывает кнопку вверх для меня, когда я иду от фрагмента A к B и добавляю A в backstack. :(
aandis
14

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

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);
                }
            }
        });
Юрий Сыч
источник
1
СПАСИБО, ТАК ЧТО это единственный, который действительно работал. Как только я добавляю это, drawerToggle.setToolbarNavigationClickListener(этот слушатель вызывается при нажатии на стрелку
EpicPandaForce
Спасибо @Yuriy, это помогло мне решить мою проблему
Мухаммед Шоаиб Муртаза
12

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

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

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

Баррич
источник
10

Попробуйте обработать выбор элемента 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);
    }
}
dzeikei
источник
+1 аккуратное решение. Для того чтобы ваш ответ был полностью применим, вы должны добавить проверку на backstack. Когда он пуст, тогда автоматически установите индикатор выдвижного ящика на true.
HpTerm
@HpTerm Я работаю с backstack, onBackPressed()потому что хотел одинакового поведения для обоих.
dzeikei
6

СЛЕДОВАТЬ ЗА

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

@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что в моем случае требовалось, иначе у меня иногда было странное поведение восходящей каретки.

HpTerm
источник
Это полный код, который мне нужно добавить для реализации поведения Up каретки, или вы ссылаетесь на один из других постов? Пожалуйста, уточните это.
JJD
Спасибо за редактирование. На самом деле я поддерживаю mDrawerToggleв NavigationDrawerFragmentклассе. Для того, чтобы получить его работу мне нужно также переключать состояние кнопки дома / индикатора - см NavigationDrawerFragment#toggleDrawerIndicator. Кроме того, я не уверен, что вам нужна первоначальная регистрация onOptionsItemSelected: я раскомментировал ее. - Упрощенный пример
JJD
Пересмотренная реализация : Вы правы в отношении первоначальной регистрации onOptionsItemSelected. Это гарантирует, что Навигационный ящик все еще открывается в иерархии верхнего уровня. Тем не менее, я переместил код в NavigationDrawerFragment#onOptionsItemSelected. Это помогает мне не подвергать mDrawerToggleк MainActivity.
JJD
@JJD Я вспомнил, как немного боролся за то, чтобы все работало на повышение уровня для каждого уровня иерархии. Пока это работает для вас тоже, это нормально. Конечно, как вы говорите, вы можете переместить код в другое место, чтобы не показывать его.
HpTerm
2

Я создал интерфейс для активности хостинга, чтобы обновить состояние просмотра меню гамбургера. Для фрагментов верхнего уровня я установил переключатель на 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);
    }

}
Билл Моте
источник
2

Этот ответ работал, но с ним была небольшая проблема. Он getSupportActionBar().setDisplayHomeAsUpEnabled(false)не был вызван явно, и это приводило к тому, что значок ящика скрывался, даже когда в backstack не было никаких элементов, поэтому изменение 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);
        }

    }
Шераз Ахмад Хилджи
источник
В моем случае Rigth решение было использовать только actionBarDrawerToggle.setDrawerIndicatorEnabled (getSupportFragmentManager (). getBackStackEntryCount () <0);
Горец
1

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

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;
}
kml_ckr
источник
1

Если вы посмотрите на приложение GMAIL и зайдете сюда, чтобы найти значок «carret / уступка».

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

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


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

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

  • secondFragment
  • третий фрагмент и пр ....

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

Позвоните на 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, чтобы снова включить значок списка навигации.

Спасибо, Пусп

EngineSense
источник
0

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

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
        // handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
0101100101
источник