In the previous article we began hooking our navigation drawer up to the ActionBar by connecting up the Up/Home button. In this concluding article we’ll change the ActionBar title, and hide any content-specific menus when the drawer is open.
To change the ActionBar title, we need to detect when the drawer settles to an open or closed state, and then change the title text accordingly. We could do this using the DrawerListener that we used previously, but we can now do this through our ActionBarDrawerToggle instance by overriding the onDrawerOpened()
and onDrawerClosed()
methods, and we can also restore our item selection:
@Override protected void onCreate(Bundle savedInstanceState) { . . . drawerToggle = new ActionBarDrawerToggle(this, drawer, R.drawable.ic_drawer, R.string.open, R.string.close) { @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); updateContent(); } @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); getActionBar().setTitle(R.string.app_name); } }; }
Now the ActionBar title will be set to the name of the currently displayed Fragment (From the names[]
array) when the drawer is hidden, but change to the app name when it is visible. This is the behaviour specified in the design guidelines.
The final thing that we need to do is make sure that any menus specific to the content panel is hidden when the drawer is visible as it could make things confusing for the user if there are actions available on content which is not in scope because the navigation drawer is visible. In our case, there is only one place where we have any context specific menu items, and that is in our CursorFragment where we have an option to add a new record to the database. To manage this we can add some simple logic to our onPrepareOptionsMenu()
method:
@Override public boolean onPrepareOptionsMenu(Menu menu) { if(drawer != null && navList != null) { MenuItem item = menu.findItem(R.id.add); if(item != null) { item.setVisible(!drawer.isDrawerOpen(navList)); } } return super.onPrepareOptionsMenu(menu); }
If we run this, we can see that the ActionBar title changes when the drawer is open and closed, and that the add item menu at the bottom of the CursorFragment is hidden when the drawer is open:
One final thing that we can do is to follow the design guidelines and start the app with the drawer open on the first invocation to aid discoverability of the navigation drawer. We’ll do this by reading a boolean value from SharedPreferences:
private static final String OPENED_KEY = "OPENED_KEY"; private SharedPreferences prefs = null; private Boolean opened = null; @Override protected void onCreate(Bundle savedInstanceState) { . . . new Thread(new Runnable() { @Override public void run() { prefs = getPreferences(MODE_PRIVATE); opened = prefs.getBoolean(OPENED_KEY, false); if(opened == false) { drawer.openDrawer(navList); } } }).start(); }
Loading of SharedPreferences involves a file read and parsing, so we keep this off the UI thread by running it in a separate thread. In practise this will happen really quickly and we could probably get away with doing in directly within onCreate()
, but we’ll adhere to best practise!
We need to add some additional code to the onDrawerClosed()
method to update the SharedPreference when the drawer is closed for the first time:
@Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); updateContent(); invalidateOptionsMenu(); if(opened != null && opened == false) { opened = true; if(prefs != null) { Editor editor = prefs.edit(); editor.putBoolean(OPENED_KEY, true); editor.apply(); } } }
We don’t need to bother doing this on a separate thread because the apply()
method will kick of an asynchronous write which will not block the UI thread. If we were to use commit()
instead of apply()
we’d need to do it on a separate thread.
Also in the design guidelines there is a sub-section of Interaction Details named “Give the user a quick peek” which indicates that if the user touches the left edge of the screen, then the drawer should peek out in order to promote accidental discovery of the navigation drawer. If we try this in our current implementation, we’ll see that we get this behaviour for free:
This raises quite an important point: There are a number of very good libraries out in the wild to provide navigation drawer on Android. At present none of them conform to the design guidelines for the simple reason that (at the time of writing) the guidelines have only been published for a matter of days. However, the navigation drawer implementation now included in the support library does conform quite closely to the design guidelines, and includes a lot of stuff, such as the peek, for free.
That concludes our short introduction to the new navigation drawer APIs. They are really quite simple to implement, while providing a lot of functionality, plus good conformance to the design guidelines.
The source code for this article can be found here.
© 2013 – 2014, Mark Allison. All rights reserved.
Copyright © 2013 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.
Hi first of all great article! I really enjoyed this series as I was using one of the Open Source libraries for one of my projects and I’m considering moving to this one.
One thing I’m still missing from Google’s library is full support for ActionBarSherlock. So I guess I’ll have to wait before someone makes it fully compatible or when google releases ActionBarCompat.
One side note, I thing you should wrap your “drawer.openDrawer(navList);” on a RunOnUiThread after reading from SharedPreferences because you are manipulating the UI within a background thread which may produce unexpected results.
Shouldn’t the drawer.openDrawer() call be included in a runOnUiThread() call, like this:
runOnUiThread(new Runnable() {
public void run() {
drawer.openDrawer(navList);
}
});
Couldn’t find the source for DrawerLayout anywhere. So I don’t know whether any UI thread safety measure is included in there already.
You may be right, however usually an exception is thrown when you try and call a UI operation from a background thread (try calling setText() on a TextView from a background thread). In this case, no exception in thrown, so I’m assuming that maybe this is managed for us.
Was playing with this tutorial and noticed that upon an orientation change I lose the current list position and it returns to the first position. Is there something that I am missing that would keep it in the same position on orientation change? It seems to me that the saved instance is not being restored for some reason but I haven’t been able to figure out why.