Перемикання між зображенням Android Navigation Drawer та каретою під час використання фрагментів


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();

Проблема, яка виникає у мене, коли я повертаюсь до фрагментів верхнього рівня, все ще відображається карат замість оригінального зображення Навігаційного ящика. Будь-які пропозиції щодо того, як "оновити" ActionBar на фрагментах верхнього рівня, щоб повторно відобразити зображення навігаційного ящика?


Рішення

Пропозиція Тома працювала на мене. Ось що я зробив:

MainActivity

Ця активність контролює всі фрагменти програми.

Готуючи нові фрагменти для заміни інших, я встановив 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замінивши setDrawerIndicatorEnabled(true)це , я повернув це вище, встановивши DrawerToggle таким чином:

@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;
         
    }

2
Також у вашому методі onBackPress () ви можете перевірити, скільки записів знаходиться у зворотному стеку методом getFragmentManager (). GetBackStackEntryCount () та ввімкнути індикатор ящика лише у випадку, якщо результат дорівнює 0. У цьому випадку не потрібно включати homeAsUpIndicator у кожному нижньому рівніFragment.
пвшник

2
Це дуже корисно! Ви повинні перемістити частину своєї публікації "рішення" і зробити її фактичною "відповіддю". Ви отримаєте більше очок за upvotes і є відповідь після того, як всі
Олексій

Чому ви замінити фрагмент тут: .replace(R.id.frag_layout. Якщо це ще один рівень ієрархії, я б очікував, що ви .addце отримаєте в backstack.
JJD

5
Брат, як ти theDrawerToggle.setDrawerIndicatorEnabled(false);посилаєшся на фрагмент всередині? Я думаю, що це задекларовано у файлі класу Основна діяльність. Я не можу знайти спосіб посилання на це. Якісь підказки?
Скайнет

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 щодо інших порад щодо поведінки індикаторів у 3 рядках.


3
Я опинився на чомусь подібному до твого. Я думаю, що це рішення (використовуючи BackStackChangedListener для переходу між показаним) є найелегантнішим. Однак у мене є дві зміни: 1) Я не дзвоню / не змінюю індикатор витяжки у викликах onDrawerClosed / onDrawerOpened - це не потрібно, і 2) я дозволяю самим фрагментам обробляти навігацію Up, роблячи AbstractFragment, який усі вони успадковують реалізує onOptionsItemSelected (..) і який завжди викликає setHasOptionsMenu (true);
Espen Riskedal

2
Також слід заблокувати рух пальцем для навігаційного ящика в режимі стрілки: mDrawerLayout.setDrawerLockMode (DrawerLayout.LOCK_MODE_LOCKED_CLOSED); і ввімкніть його знову в режимі індикатора ящика
artkoenig

5
Я закінчила все це у своєму фрагменті NavigationDrawer. Працює як принадність, спасибі.
frostymarvelous

1
setActionBarArrowDependingOnFragmentsBackStack()... яке довге ім’я: P
S.Thiongane

2
Це не відображає для мене кнопку вгору, коли я переходжу від фрагмента А до В і додаю "А" в 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

Спробуйте обробити вибір елемента «Головна» в 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);
    }
}

+1 акуратне рішення. Щоб ваша відповідь була повноцінно використаною, вам слід додати чек на задньому плані. Коли він порожній, тоді автоматично встановіть індикатор шухляди на істинний.
HpTerm

@HpTerm Я обробляю рюкзак, 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);
    }
}

EDIT

На питання про @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які в моєму випадку вимагали, інакше я інколи мав дивні способи поведінки з нагодою.


Це повний код потрібно додати , щоб реалізувати 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)було не викликано явно, і це спричиняло приховування піктограми шухляди, навіть якщо в рюкзаку не було предметів, тому зміна 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;
}

1

Якщо ви подивитеся на додаток GMAIL і завітайте сюди, щоб шукати піктограму «carret / priuntice».

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

  • NavigationDrawer -> Перегляд списку містить підфрагменти.


  • підфрагменти будуть перераховані так

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

  • Другий Фрагмент
  • третійФрагмент тощо.

У 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;
}

У методі активності OnBackPress Drawer встановіть перемикач ящика на true, щоб знову включити значок списку навігації.

Дякую, Пусп



0

IMO, використовуючи OnNavigateUp () (як показано тут ) у рівноденніку чи розчині Тома, чистіший і, здається, працює краще. Просто замініть код onOptionsItemSelected цим:

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
        // handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.