177

When using the Navigation Drawer the Android devs are recommending that in the ActionBar "only those screens that are represented in the Navigation Drawer should actually have the Navigation Drawer image" and that "all other screens have the traditional up carat."

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

I'm using one activity to control multiple levels of fragments and can get the Navigation Drawer image to display and function at all levels.

When creating lower level fragments I can call the ActionBarDrawerToggle setDrawerIndicatorEnabled(false) to hide the Navigation Drawer image and have the Up caret displayed

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

The problem I'm having is when I navigate back to the top level fragments the Up carat still shows instead of the original Navigation Drawer image. Any suggestions on how to "refresh" the ActionBar on the top level fragments to re-display the Navigation Drawer image?


Solution

Tom's suggestion worked for me. Here’s what I did:

MainActivity

This activity controls all fragments in the app.

When preparing new fragments to replace others, I set the DrawerToggle setDrawerIndicatorEnabled(false) like this:

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

Next, in an override of onBackPressed, I reverted the above by setting the DrawerToggle to setDrawerIndicatorEnabled(true) like this:

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

In the LowerLevelFragments

In the fragments I modified onCreate and onOptionsItemSelected like this:

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;
        … 
    }
JJD
  • 50,076
  • 60
  • 203
  • 339
EvilAsh
  • 1,773
  • 2
  • 11
  • 5
  • 2
    Also in your onBackPressed() method you can check how many entries are in back stack with getFragmentManager().getBackStackEntryCount() method and enable drawer indicator only if result is 0. In that case it's unnecessary to enable homeAsUpIndicator in each LowerLevelFragments. – pvshnik Aug 30 '13 at 03:46
  • 3
    This is very useful! You should move the "solution" part of your post and make it an actual "answer". You'll get more points for upvotes and it **is** an answer after all – Oleksiy May 14 '14 at 20:46
  • Why are you replacing the fragment here: `.replace(R.id.frag_layout`. If this is one more hierarchy level I would expect that you `.add` it to the backstack. – JJD May 27 '14 at 09:37
  • 5
    Bro, how do you reference the `theDrawerToggle.setDrawerIndicatorEnabled(false);` inside the fragment? I think it is declared in the Main Activity class file. I cant find a way to reference this. Any hints? – Skynet Oct 30 '14 at 09:30
  • 1
    When using a toolbar I had to switch the display options to not use the home as up in the meantime. Otherwise the `setDisplayOptions()` method within the `ToolbarWidgetWrapper` (of the internal android.support.v7.internal.widget package) wouldn't recreate the icon when entering the same fragment a second time. Just leaving this here for when others stumble upon this problem as well. – Wolfram Rittmeyer Mar 03 '15 at 20:08
  • @Wolfram Rittmeyer, could you explain in detail what you have done to solve the issue when using toolbar? – Sanif SS Aug 31 '16 at 10:05

12 Answers12

83

It's easy as 1-2-3.

If you want to achieve:

1) Drawer Indicator - when no fragments are in the Back Stack or the Drawer is opened

2) Arrow - when some Fragments are in the Back Stack

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) Both indicators to act according to their shape

@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. See Creating a Navigation Drawer on Android Developers on other tips about the 3-lines indicator behavior.

riwnodennyk
  • 8,140
  • 4
  • 35
  • 37
  • 3
    I ended up on something similar to yours. I think this solution (using the BackStackChangedListener to flip between what is shown) is the most elegant. However I have two changes: 1) I don't call/change the drawer indicator in the onDrawerClosed/onDrawerOpened calls - it is not needed, and 2) I let the Fragments themselves handle the Up navigation by making an AbstractFragment they all inherit which implements onOptionsItemSelected(..) and which always calls setHasOptionsMenu(true); – Espen Riskedal Nov 25 '13 at 19:17
  • Using BackStackChangedListener worked the best for me. – Marcel Bro Mar 04 '14 at 19:50
  • 2
    Yout should also lock the swipe gesture for the navigation drawer in arrow mode: mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); and enable it again in drawer indicator mode – artkoenig Jun 11 '14 at 19:44
  • @ArtJom not a good idea. That's why the drawer indicatoris changed in the onDrawerClosed/onDrawerOpened – frostymarvelous Jun 24 '14 at 11:15
  • @EspenRiskedal see comment reply to ArtJom above – frostymarvelous Jun 24 '14 at 11:15
  • 5
    I ended up doing all this in my NavigationDrawer fragment. Works like a charm btw, thanks. – frostymarvelous Jun 24 '14 at 11:16
  • This concrete solution has a drawback. It doesn't care about the order of fragments in the back stack: in case there is the only "main" fragment all is ok, however you possibly can have several "main" fragments (which are navigated from the nav.drawer). So, you have to care about that, for example clear the backstack on nav.drawer item's click – Dmitry Gryazin Jul 31 '14 at 11:34
  • Thanks for your answer.I need to change the actionbar icons in innerFragment. when i call setHasOptionMenu(true) from innerFragment get NullPointerException in onPrepareOptionMenu. @riwnodennyk – Divya Aug 07 '14 at 08:58
  • Thanks for your answer.It is working perfect. How to change that arrow icon with my custom image.@iwnodennyk – Divya Sep 12 '14 at 12:42
  • @Bagzerg isn't that (clear backstack upon clicking item from drawer) what the design guidelines say? I think they do. – Sufian Oct 03 '14 at 12:31
  • I was banging my head against a wall since three days and then I landed here. – Skynet Oct 30 '14 at 10:24
  • 1
    `setActionBarArrowDependingOnFragmentsBackStack()` ... what a long name :P – S.Thiongane Dec 22 '14 at 09:48
  • This is what worked for me but i made a little tweak to make it better which is mentioned here http://stackoverflow.com/a/29025687/1118886 – Sheraz Ahmad Khilji Mar 13 '15 at 05:50
  • 2
    This doesn't show the up button for me when I go from fragment A to B and add A to backstack. :( – aandis Jun 14 '15 at 14:20
  • 1
    @zack for me the above solution was not enough. In order to show up button I follow this strange (but working) solution: http://stackoverflow.com/a/29594947/1223140 . Later on the up arrow was not clickable, so I followed this solution: http://stackoverflow.com/a/33872425/1223140 and finally got it working. – Dale Cooper Mar 25 '16 at 11:11
  • Thanks. Please, rewrite onOptionsItemSelected. It usually contains switch for item id. In your case it contains only one item (android.R.id.home) and popBackStackImmediate(). It's not a good situation. For instance, I replaced this logic with onBackPressed(), where some new conditions are held. – CoolMind Aug 02 '16 at 11:29
30

You have written that, to implement lower-level fragments, you are replacing the existing fragment, as opposed to implementing the lower-level fragment in a new activity.

I would think that you would then have to implement the back functionality manually: when the user pressed back you have code that pops the stack (e.g. in Activity::onBackPressed override). So, wherever you do that, you can reverse the setDrawerIndicatorEnabled.

JJD
  • 50,076
  • 60
  • 203
  • 339
Tom
  • 17,103
  • 8
  • 67
  • 75
  • Thanks Tom, that worked! I've updated the original post with the solution I used. – EvilAsh Jun 30 '13 at 15:08
  • 6
    How to reference the theDrawerToggle in a lower level fragment? It has been defined in the Main activity, cant get my head around this! – Skynet Oct 30 '14 at 09:40
  • 1
    You can get the activity from the fragment. So just add a getter in your main activity to access to the drawer toggle. – Raphael Royer-Rivard Jun 28 '15 at 18:48
14

I've used next thing:

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);
                }
            }
        });
Yuriy Sych
  • 140
  • 1
  • 6
12

If your up action bar button doesn't work, don't forget to add the listener :

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

I've got some trouble implementing a drawer navigation with a home button, everything worked except the action buton.

Burrich
  • 1,214
  • 1
  • 16
  • 15
10

Try handling the Home item selection in the MainActivity depending on the state of the DrawerToggle. This way you don't have to add same code to every fragment.

@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
  • 2,256
  • 1
  • 21
  • 27
  • +1 neat solution. For your answer to be fully usable you should add a check on the backstack. When it is empty then automatically set the drawer indicator to true. – HpTerm Oct 02 '13 at 07:55
  • @HpTerm I handle the backstack in `onBackPressed()` because I wanted the same behavior for both. – dzeikei Oct 03 '13 at 01:06
6

FOLLOW UP

The solution given by @dzeikei is neat, but it can be extended, when using fragments, to automatically handle setting back the drawer indicator when the backstack is empty.

@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

For the question of @JJD.

The fragments are held/managed in an activity. The above code is written once in that activity, but only handle the up caret for the onOptionsItemSelected.

In one of my apps I also needed to handle the behavior of the up caret when the back button was pressed. This can be handle by overriding 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);
        }
    }
};

Note the code duplication between onOptionsItemSelected and onBackPressed which can be avoided by creating a method and calling that method in both places.

Also note I add two more times executePendingTransactions which in my case was required or else I had sometimes strange behaviors of the up caret.

JJD
  • 50,076
  • 60
  • 203
  • 339
HpTerm
  • 8,151
  • 12
  • 51
  • 67
  • Is this the full code I need to add to implement the **Up** caret behavior or are you refering to one of the other posts? Please clarify this. – JJD May 27 '14 at 14:47
  • Thanks for the edit. Actually I maintain `mDrawerToggle` within a `NavigationDrawerFragment` class. To get it working I need to also toggle the state of the home button/indicator - see: `NavigationDrawerFragment#toggleDrawerIndicator`. Further, I am not sure you need the initial check in `onOptionsItemSelected`: I uncommented it. - [Simplified example](http://pastebin.com/7Ughhn5D) – JJD May 28 '14 at 11:40
  • [Revised implementation](http://pastebin.com/9GgC8624): You are right about the initial check in `onOptionsItemSelected`. This ensures that the Navigation Drawer still opens in the top-level hierarchy. However, I moved the code into the `NavigationDrawerFragment#onOptionsItemSelected`. This helps me not to expose `mDrawerToggle` to the `MainActivity`. – JJD May 28 '14 at 12:11
  • @JJD I remembered struggling a little bit having everything working for the up caret for each level of the hierarchy. As long as it works for you too that's fine. Of course, as you say, you can move the code elsewhere not to expose it. – HpTerm May 28 '14 at 12:42
2

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

Then in my Activity ...

public class MainActivity extends Activity implements SomeFragment.OnFragmentInteractionListener {

    private ActionBarDrawerToggle mDrawerToggle;

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

}
Bill Mote
  • 12,644
  • 7
  • 58
  • 82
2

This answer was working but there was a little problem with it. The getSupportActionBar().setDisplayHomeAsUpEnabled(false) was not called explicitly and it was causing drawer icon to be hidden even when there were no items in the backstack so changing the setActionBarArrowDependingOnFragmentsBackStack() method worked for me.

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

    }
Community
  • 1
  • 1
Sheraz Ahmad Khilji
  • 8,300
  • 9
  • 52
  • 84
  • in my case rigth solution was using only actionBarDrawerToggle.setDrawerIndicatorEnabled(getSupportFragmentManager().getBackStackEntryCount() < 0); – Gorets Dec 07 '16 at 19:20
1

Logic is clear. Show back button if fragment back stack is clear. Show material hamburger-back animation if fragment stack is not clear.

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
  • 2,211
  • 3
  • 22
  • 35
1

If you take a look at the GMAIL app and come here to search for the carret/affordance icon..

I would ask you to do this, none of the above answer was clear. i was able to modify the accepted answer.

  • NavigationDrawer --> Listview contains subfragments.


  • subfragments will be listed like this

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

  • secondFragment
  • thirdFragment and so forth....

In firstFragment you have other fragment.

Call this on DrawerActivity

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

and in fragment

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

On the OnBackPressed Drawer activity method set the drawer toggle to true to enable navigation list icon again.

Thanks, Pusp

EngineSense
  • 3,266
  • 8
  • 28
  • 44
1

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

oskarko
  • 3,382
  • 1
  • 26
  • 26
0

IMO, using onNavigateUp() (as shown here) in riwnodennyk's or Tom's solution is cleaner and seems to work better. Just replace the onOptionsItemSelected code with this:

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
        // handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
Community
  • 1
  • 1
0101100101
  • 5,786
  • 6
  • 31
  • 55