Переключення між зображенням ящика Android навігації та кнопкою "Вгору" при використанні фрагментів

Під час використання ящика навігації пристрої Android рекомендують, щоб в ActionBar "лише ті екрани, які відображаються в ящику навігації, повинні мати зображення з ящиком навігації" і що "всі інші екрани мають традиційну карат".

See here for details: http://youtu.be/F5COhlbpIbY

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

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

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", а не оригінальний образ ящика для навігації. Будь-які пропозиції щодо того, як "оновити" 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 У LowerLevelFragments
    setDrawerIndicatorEnabled(true)
}

У LowerLevelFragments

У фрагментах я змінив onCreate та onOptionsItemSelected таким чином:

In onCreate added setHasOptionsMenu(true) to enable configuring the options menu. Also set setDisplayHomeAsUpEnabled(true) to enable the < in the actionbar:

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

Then in onOptionsItemSelected whenever the < is pressed it calls the onBackPressed() from the activity to move up one level in the hierarchy and display the Navigation Drawer Image:

@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;
        … 
    }
173
Чому ви замінюєте фрагмент тут: .replace (R.id.frag_layout ). Якщо це ще один рівень ієрархії, я б очікував, що ви .add повернете його назад.
додано Автор JJD, джерело
Любите вас, щоб запитати це питання <3
додано Автор Broak, джерело
Бру, як ви вказуєте theDrawerToggle.setDrawerIndicatorEnabled (false); всередині фрагмента? Я думаю, це оголошено в файлі класу основної діяльності. Я не можу знайти спосіб посилання на це. Будь-які натяки?
додано Автор Skynet, джерело
Це дуже корисно! Ви повинні перемістити частину "вирішення" своєї публікації і зробити це справжньою "відповіддю". Ви отримаєте більше очок для upvotes, і це відповість нарешті
додано Автор Oleksiy, джерело
При використанні панелі інструментів мені довелося переключити параметри відображення, щоб тимчасово не використовувати будинок. Інакше метод setDisplayOptions() в межах поля ToolbarWidgetWrapper (внутрішнього пакету android.support.v7.internal.widget) не відтворює піктограму при введенні того ж фрагмента a другий раз. Просто залиште це тут, коли інші наткнуться на цю проблему.
додано Автор Wolfram Rittmeyer, джерело
@ Вольфрам Ритмейер, чи могли б ви докладно пояснити, що ви зробили, щоб вирішити цю проблему під час використання панелі інструментів?
додано Автор Sanif SS, джерело
Крім того, у вашому методі onBackPressed() ви можете перевірити, скільки записів знаходиться у зворотному стекі за допомогою методу getFragmentManager() getBackStackEntryCount() і увімкнути показник ящика тільки в тому випадку, якщо результат дорівнює 0. У цьому випадку не потрібно вмикати homeAsUpIndicator у кожному LowerLevelFragments.
додано Автор pvshnik, джерело

12 Відповіді

Це легко, як 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);
    }
}

П.С. Див. Розділ Створення ящика навігації у розробників Android для інших порад щодо поведінки індикаторів з трьома рядками .

83
додано
Yout також повинен заблокувати рух за рухомий стовпчик для навігації в режимі стрілки: mDrawerLayout.setDrawerLockMode (DrawerLayout.LOCK_MODE_LOCKE & zwnj; D_CLOSED); і знову ввімкніть його в режимі індикатора ящика
додано Автор artkoenig, джерело
Через три дні я ступав головою біля стіни, а потім я приземлився тут.
додано Автор Skynet, джерело
@ Багзерг це не так (чітко натискайте на клавіатурі, клацнувши пункт з ящика), які правила керівництва говорять? Я думаю, що вони роблять.
додано Автор Sufian, джерело
setActionBarArrowDependingOnFragmentsBackStack() ... яка довга назва: P
додано Автор S.Thiongane, джерело
Дякую. Будь ласка, перепишіть наOptionsItemSelected. Він зазвичай містить перемикач для ідентифікатора елемента. У вашому випадку він містить лише один елемент (android.R.id.home) і popBackStackImmediate (). Це не дуже хороша ситуація. Наприклад, я замінив цю логіку на onBackPressed (), де зберігаються деякі нові умови.
додано Автор CoolMind, джерело
Це щось для мене, але я зробив трохи налаштування, щоб зробити його кращим, який тут згадується stackoverflow.com/a/29025687/1118886
додано Автор Sheraz Ahmad Khilji, джерело
@Зак для мене вищенаведене рішення було недостатньо. Щоб відобразити кнопку, я стежу за цим дивним (але працюючим) рішенням: stackoverflow.com/a/29594947/1223140 . Пізніше стрілка вгору не була натискана, тому я використав це рішення: stackoverflow.com/a/33872425/1223140 і, нарешті, це працює.
додано Автор Dale Cooper, джерело
Це не показує кнопки "вгору" для мене, коли я йду з фрагмента А до B, і додаю A, щоб повернути назад. :(
додано Автор aandis, джерело
@ArtJom не гарна ідея. Ось чому індикатор шухляди змінено у onDrawerClosed/onDrawerOpened
додано Автор frostymarvelous, джерело
Я закінчив робити все це у своєму фрагменті NavigationDrawer. Працює як чарівність, спасибі.
додано Автор frostymarvelous, джерело
@EspenRiskedal див. Коментар до ArtJom вище
додано Автор frostymarvelous, джерело
Використання BackStackChangedListener працювало найкраще для мене.
додано Автор Marcel Bro, джерело
Це конкретне рішення має недолік. Це не стосується порядку фрагментів у зворотному стекі: у випадку, якщо є єдиний "основний" фрагмент, все в порядку, однак у вас, можливо, є кілька "основних" фрагментів (які переміщуються з nav.drawer). Отже, ви повинні дбати про це, наприклад, очистити задній стовпчик на клацанні пункту nav.drawer
додано Автор Dmitry Gryazin, джерело
Я опинився на щось подібне до твого. Я думаю, що це рішення (використовуючи BackStackChangedListener, щоб перевернути між тим, що показано) є найбільш елегантним. Проте в мене є дві зміни: 1) Я не дзвоню/не змінюю індикатор ящика у викликах onDrawerClosed/onDrawerOpened - це не потрібно, і 2) я дозволяю Fragments самі керувати навігацією Up, зробивши абстрактний фрагмент, який вони успадковують, який реалізує наOptionsItemSelected (..) і який завжди викликає setHasOptionsMenu (true);
додано Автор Espen Riskedal, джерело
Дякую за вашу відповідь. Він працює чудово. Як змінити цю піктограму зі стрілкою зі своїм власним зображенням. @ Iwnodennyk
додано Автор Divya, джерело
Дякую за вашу відповідь. Мені потрібно змінити значки панелі дій на внутрішньому фрагменті. коли я закликаю setHasOptionMenu (true) з internalFragment отримати NullPointerException в onPrepareOptionMenu. @ тривалість
додано Автор Divya, джерело

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

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

27
додано
Як послатися на theDrawerToggle у фрагменті нижчого рівня? Це було визначено в основній діяльності, не можу отримати мою голову навколо цього!
додано Автор Skynet, джерело
Ви можете отримати активність з фрагмента. Тож просто додати геттера у свою основну діяльність, щоб перейти до перемикача ящиків.
додано Автор Raphael Royer-Rivard, джерело
Спасибі Том, що працював! Я оновив початковий пост, використовуючи рішення, яке я використав.
додано Автор EvilAsh, джерело

Я використав наступну річ:

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);
                }
            }
        });
14
додано
ДЯКУЄТЬСЯ ТОЧНО, це єдиний, який дійсно працював. Після того як я додати це, drawerToggle.setToolbarNavigationClickListener () цей слухач викликається при натисканні стрілки
додано Автор EpicPandaForce, джерело

Якщо кнопка панелі дій не працює, не забудьте додати слухача:

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

У мене є деякі проблеми, пов'язані з навігацією ящиком з кнопкою додому, все працювало крім дії buton.

12
додано

Спробуйте використати вибір головного елемента в 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);
    }
}
10
додано
О, вірно, я пропустив цей пункт. Дякую.
додано Автор HpTerm, джерело
+1 акуратне рішення. Щоб ваша відповідь була повною мірою корисною, ви повинні додати чек на задній панель. Коли він порожній, автоматично встановіть індикатор ящика на істину.
додано Автор HpTerm, джерело
@HpTerm Я виконую зворотний скидання в onBackPressed() , тому що я хотів би таку ж поведінку для обох.
додано Автор dzeikei, джерело

НАСТУПНИЙ

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

7
додано
Повторна реалізація : ви маєте рацію про початкову перевірку в onOptionsItemSelected . Це гарантує, що ящик навігації все ще відкривається в ієрархії верхнього рівня. Однак, я перемістив код на NavigationDrawerFragment # onOptionsItemSelected . Це допомагає мені не виставляти mDrawerToggle до MainActivity .
додано Автор JJD, джерело
Дякую за редагування. Фактично я підтримую mDrawerToggle в класі NavigationDrawerFragment . Для того, щоб це працювало, потрібно також переключити стан кнопки/індикатора будинку - див. NavigationDrawerFragment # toggleDrawerIndicator . Крім того, я не впевнений, що вам потрібна початкова перевірка в onOptionsItemSelected : я її розкоментував. - спрощений приклад
додано Автор JJD, джерело
Чи це повний код, який потрібно додати для впровадження поведінки касети Up , чи ви посилаєтеся на одне з інших повідомлень? Будь ласка, поясніть це.
додано Автор JJD, джерело
@JJD Я згадав, що намагаюсь трохи попрацювати, щоб все працювало для кожного рівня ієрархії. До тих пір, поки це працює для вас, це теж добре. Звичайно, як ви кажете, ви можете перемістити код в інше місце, щоб не піддавати його.
додано Автор HpTerm, джерело
@JJD перевірити мій редагування
додано Автор HpTerm, джерело

I created an interface for the hosting activity to update the view state of the hamburger menu. For top level fragments I set the toggle to true and for fragments for which I want to display the up < arrow I set the toggle to 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);
        }

    }
2
додано
у моєму випадку рішення rigth використовувало лише actionBarDrawerToggle.setDrawerIndicatorEnabled (getSupportFr & amp; zwnj; agmentManager (). getB & actu; ackStackEntryCount() <0);
додано Автор Gorets, джерело

You can look at this little example! https://github.com/oskarko/NavDrawerExample

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/affordance.

Я б просив вас зробити це, ніхто з вищевказаної відповіді не був ясним. я зміг змінити прийняту відповідь.

  • NavigationDrawer --> Listview contains subfragments.


  • subfragments will be listed like this

  • firstFragment == position 0 ---> this will have subfragments --> fragment

  • secondFragment
  • thirdFragment and so forth....

У першому фрагменті у вас є інший фрагмент.

Назовите це на 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 встановіть перемикач drawer на значення true, щоб знову включити піктограму навігаційного списку.

Дякую, Півп

1
додано

ІМО, використовуючи onNavigateUp() (як показано у розділі тут ) в ріуноденник або Tom's рішення є чистішим і, здається, працює краще. Просто замініть наOptionsItemSelected на цей код:

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
       //handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
0
додано
android_jobs_ua
android_jobs_ua
120 учасників

Публикуем вакансии и запросы на поиск работы по направлению Android. Здесь всё: full-time, part-time, remote и разовые подработки.

Mobile Dev Jobs UA
Mobile Dev Jobs UA
20 учасників

Публикуем вакансии и запросы на поиск работы по направлению iOS, Android, Xamarin, RN и т.д.