When Google announced that Android apps should follow their Material Design standards, they did not give the developers a lot of tools to actually implement this new look and feel. Of course Google’s own apps were all quickly updated and looked amazing, but the rest of us were left with little more than fancy design guidelines and no real components to use in our apps.
So last weeks release of the Android Design Support Library came as a relief to many. It promises to help us quickly create nice looking apps that are consistent with the rest of the platform, without having to roll everything for ourselves. Think of it as AppCompat’s UI-centric companion.
The NavigationView is the part of this library which I thought the most interesting. It helps you create the slick sliding-over-everything navigation drawer that is such a recognizable part of material apps. I will demonstrate how to use this component and how to avoid some common mistakes.
(function(d, t) {
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
g.src = ‘http://assets.gfycat.com/js/gfyajax-0.517d.js’;
s.parentNode.insertBefore(g, s);
}(document, ‘script’));
Basic Setup
The basic setup is pretty straightforward, you add a DrawerLayout and NavigationView to your main layout resource:
[xml]
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!– The main content view –>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!– Toolbar instead of ActionBar so the drawer can slide on top –>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/abc_action_bar_default_height_material"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/AppTheme.Toolbar"
app:titleTextAppearance="@style/AppTheme.Toolbar.Title"/>
<!– Real content goes here –>
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
<!– The navigation drawer –>
<android.support.design.widget.NavigationView
android:id="@+id/navigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@color/ternary"
app:headerLayout="@layout/drawer_header"
app:itemIconTint="@color/drawer_item_text"
app:itemTextColor="@color/drawer_item_text"
app:menu="@menu/drawer"/>
</android.support.v4.widget.DrawerLayout>
[/xml]
And a drawer.xml menu resource for the navigation items:
[xml]
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!– group with single selected item so only one item is highlighted in the nav menu –>
<group android:checkableBehavior="single">
<item
android:id="@+id/drawer_item_1"
android:icon="@drawable/ic_info"
android:title="@string/item_1"/>
<item
android:id="@+id/drawer_item_2"
android:icon="@drawable/ic_help"
android:title="@string/item_2"/>
</group>
</menu>
[/xml]
Then wire it up in your Activity. Notice the nice onNavigationItemSelected(MenuItem) callback:
[java]
public class MainActivity extends AppCompatActivity implements
NavigationView.OnNavigationItemSelectedListener {
private static final long DRAWER_CLOSE_DELAY_MS = 250;
private static final String NAV_ITEM_ID = "navItemId";
private final Handler mDrawerActionHandler = new Handler();
private DrawerLayout mDrawerLayout;
private ActionBarDrawerToggle mDrawerToggle;
private int mNavItemId;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// load saved navigation state if present
if (null == savedInstanceState) {
mNavItemId = R.id.drawer_item_1;
} else {
mNavItemId = savedInstanceState.getInt(NAV_ITEM_ID);
}
// listen for navigation events
NavigationView navigationView = (NavigationView) findViewById(R.id.navigation);
navigationView.setNavigationItemSelectedListener(this);
// select the correct nav menu item
navigationView.getMenu().findItem(mNavItemId).setChecked(true);
// set up the hamburger icon to open and close the drawer
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, toolbar, R.string.open,
R.string.close);
mDrawerLayout.setDrawerListener(mDrawerToggle);
mDrawerToggle.syncState();
navigate(mNavItemId);
}
private void navigate(final int itemId) {
// perform the actual navigation logic, updating the main content fragment etc
}
@Override
public boolean onNavigationItemSelected(final MenuItem menuItem) {
// update highlighted item in the navigation menu
menuItem.setChecked(true);
mNavItemId = menuItem.getItemId();
// allow some time after closing the drawer before performing real navigation
// so the user can see what is happening
mDrawerLayout.closeDrawer(GravityCompat.START);
mDrawerActionHandler.postDelayed(new Runnable() {
@Override
public void run() {
navigate(menuItem.getItemId());
}
}, DRAWER_CLOSE_DELAY_MS);
return true;
}
@Override
public void onConfigurationChanged(final Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == android.support.v7.appcompat.R.id.home) {
return mDrawerToggle.onOptionsItemSelected(item);
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(NAV_ITEM_ID, mNavItemId);
}
}
[/java]
Extra Style
This setup results in a nice-looking menu with some default styling. If you want to go a bit further, you can add a header view to the drawer and add some colors to the navigation menu itself:
[xml]
<android.support.design.widget.NavigationView
android:id="@+id/navigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@color/drawer_bg"
app:headerLayout="@layout/drawer_header"
app:itemIconTint="@color/drawer_item"
app:itemTextColor="@color/drawer_item"
app:menu="@menu/drawer"/>
[/xml]
Where the drawer_item color is actually a ColorStateList, where the checked state is used by the current active navigation item:
[xml]
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/drawer_item_checked" android:state_checked="true" />
<item android:color="@color/drawer_item_default" />
</selector>
[/xml]
Open Issues
The current version of the library does come with its limitiations. My main issue is with the system that highlights the current item in the navigation menu. The itemBackground attribute for the NavigationView does not handle the checked state of the item correctly: somehow either all items are highlighted or none of them are. This makes this attribute basically unusable for most apps. I ran into more trouble when trying to work with submenu’s in the navigation items. Once again the highlighting refused to work as expected: updating the selected item in a submenu makes the highlight overlay disappear altogether. In the end it seems that managing the selected item is still a chore that has to be solved manually in the app itself, which is not what I expected from what is supposed to be a drag-and-drop component aimed to take work away from the developers.
Conclusion
I think the NavigationView component missed the mark a little. My initial impression was pretty positive: I was able to quickly put together a nice looking navigation menu with very little code. The issues with the highlighting of the current item makes it more difficult to use than I would expect, but let’s hope that these quirks are removed in an upcoming release of the design library.
You can find the complete source code of the demo project on GitHub: github.com/smuldr/design-support-demo.
I don’t know whether this is a bug or not but I am unable to add menu items to a submenu using the new navigation view. Here is an example of something I tried. Nothing appears to work.
mNavMenu = mNavigationView.getMenu();
mContactMenu = mNavMenu.addSubMenu(“Contacts”);
mContactMenu.add(“Test”);
mContactMenu.add(“Test 2”);
You are right! This is definitely a bug. It seems the navigation menu only displays the “Contacts” header but not the sub menu items. Funny thing is: when you add another item to the menu, suddenly the sub menu items do appear.
You might work around the issue by adding a dummy item after you add the sub menu items, and removing it straight away:
Menu navMenu = navigationView.getMenu();
SubMenu contactsMenu = navMenu.addSubMenu(“Contacts”);
contactsMenu.add(“Item 1”);
contactsMenu.add(“Item 2”);
// now add and remove a dummy item to force display of the sub items
navMenu.add(0, 42, 0, “removeme”);
navMenu.removeItem(42);
mdrawertoggle.syncstate not working properly, when i click the drawer icon it open the drawer but when i again click the drawer icon it should close the drawer but that is not happening… i had to select one item to close the drawer .is there any fix and i also want to know which is this better approach to make navigation drawer should i use recycleview to populate the navigation drawer item but i feel ur technique is easy and simple..
I am not sure I completely understand your problem. As you can see in the GIF, the drawer manu completely covers the drawer toggle. There is no way to use that toggle to close the menu. Problem solved!
Nevertheless, I did some tests by changing the layout so the toolbar remains visible when the menu is open, but then I still cannot reproduce your problem. The drawer opens and closes exactly as it should every time you press the hamburger. Have you tried building the sample project on GitHub? https://github.com/smuldr/design-support-demo
Hello, how to set navigation view to show below of toolbar?
Thanks!
Marcos
Have you tried putting the Toolbar outside of the DrawerLayout?
See: https://gist.github.com/smuldr/1390b12d6fdda8af8b03
Put Navigationview below DrawerLayout. And put NavigationView and DrawerLayout in a LinearLayout.
Hi, I put my toolbar outside of the DrawerLayout but now I have an empty space between the toolbar and the DrawerLayout (with a header).
I check for paddings and margins and I don’t have any one.
Do you know how can I remove this padding ?
Thanks,
Lizeth
@Marcos – I accomplished this by adding a margin to the Navigation view: android:layout_marginTop=”?attr/actionBarSize”
Hi!
How can I use NavigationView with ActionBar and Activity (instead of SupportActionBar, AppCompatActivity)?
Thanks
I am afraid you will have a bad time when you try to use the design library without the AppCompat support library cmponents. For example: the ActionBarDrawerToggle and the DrawerLayout are both tied to the AppCompat library.
Do you have a special reason to want to get rid of the AppCompat library? I find it relatively painless to use…
How do I change the color of the selection effect , including the color of the ripple (Lollipop and above) ?
I guess I am late in replying but you can get this effect by using:
app:itemBackground=”@drawable/list_selector”
Hi,
I’m trying to build the menu programmatically (without the xml). Have you had any luck? I can’t figure out how to style the individual items. And there’s no way to retrieve the menu item position as far as I can tell.
In addition, is it possible to style the individual menu items from an xml (but add the data programmatically) ?
Thanks!
You can get hold of NavigationView and NavigationMenu like this.
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
Menu navigationMenu = navigationView.getMenu();
Then can use
navigationMenu.add( );
I have tried this for first level of menus and it works for me. I have not tried this for submenus.
Great Blog steven! Thanks
Just wanted to add app:itemBackground=”@drawable/list_selector”
worked for me while customizing navigation drawer.
It however had the same bug as you mentioned when i did it via code.
I am using the Android Studio’s template of ‘Navigation Drawer Activity’. My problem is , whenever I switch to an activity from an nav item and press the physical back button from there, after coming back to Main Activity , the last pressed item stays highlighted which I don’t want. This is not the case when the soft ‘home’ or back button pressed in the child activity. How should I crack it?
I have been facing the same problem, and I don’t think there is a built-in solution for it. It is a design library after all, not a complete navigation framework.
In the end I just keep track of the previous selected item and in the onActivityResult() method I update the highlight again. Hope that helps!