By now most of us are familiar with Navigation Drawers as they’ve been in use by Facebook, Gmail, Google Play Music, Google Play Music and tons of others apps. It’s that sliding panel that comes out onto the screen when you swipe a finger from the left edge of the screen (or sometimes right edge) or tap on the 3 bar button (sometimes called hamburger icon) in the Action Bar. If still confused then check out the design guide and you’ll know what I’m referring to. The design guide will also tell you when to use it and when not to use it.
In this article I’ll go through how to create a navigation drawer (also referred to as sidebar menu, slide-out menu, sliding menu, hamburger menu, basements, etc.) for your own apps using the DrawerLayout
APIs in the support library. But before getting into how to create a navigation (hamburger) drawer, you should not only go through the design guide but also read a couple of articles on how you might be making a wrong decision by using a hamburger menu and you could possibly resort to other methods to avoid it:
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
- https://lmjabreu.com/post/why-and-how-to-avoid-hamburger-menus/
- https://redbooth.com/blog/hamburger-menu-iphone-app
- http://blog.manbolo.com/2014/06/30/apple-on-hamburger-menus
- http://techcrunch.com/2014/05/24/before-the-hamburger-button-kills-you/
- http://ux.stackexchange.com/questions/41712/hamburger-menu-is-it-a-good-thing
- http://thenextweb.com/dd/2014/04/08/ux-designers-side-drawer-navigation-costing-half-user-engagement/
- http://exisweb.net/mobile-menu-abtest
- http://exisweb.net/menu-eats-hamburger
- https://econsultancy.com/blog/65511-hamburger-menus-for-mobile-navigation-do-they-work/
That’s a lot to read (but won’t take much time) and too much negativity around navigation drawers. But if you think you’re fixed on using it because it does make sense in several cases, then let’s head towards creating one on Android. Here’s a screenshot of the navigation drawer we’ll create (with an Action Bar).
Simple and Clean!
DrawerLayout ViewGroup in Your Layout
We’ll first code our layout in res/layout/activity_main.xml
. In order to build a drawer, we’ll have to use a DrawerLayout as the root view of our layout which’ll contain two child ViewGroups where one of them will represent the main content, whereas the other will contain the contents of the navigation drawer.
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawerLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- The main content view --> <RelativeLayout android:id="@+id/mainContent" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- The navigation drawer --> <RelativeLayout android:layout_width="280dp" android:layout_height="match_parent" android:id="@+id/drawerPane" android:layout_gravity="start"> <!-- Profile Box --> <RelativeLayout android:id="@+id/profileBox" android:layout_width="match_parent" android:layout_height="100dp" android:background="@color/material_blue_grey_800" android:padding="8dp" > <ImageView android:id="@+id/avatar" android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/ic_launcher" android:layout_marginTop="15dp" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="42dp" android:layout_centerVertical="true" android:layout_marginLeft="15dp" android:layout_toRightOf="@+id/avatar" android:orientation="vertical" > <TextView android:id="@+id/userName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Rishabh" android:textColor="#fff" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/desc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginTop="4dp" android:text="View Profile" android:textColor="#fff" android:textSize="12sp" /> </LinearLayout> </RelativeLayout> <!-- List of Actions (pages) --> <ListView android:id="@+id/navList" android:layout_width="280dp" android:layout_height="match_parent" android:layout_below="@+id/profileBox" android:choiceMode="singleChoice" android:background="#ffffffff" /> </RelativeLayout> </android.support.v4.widget.DrawerLayout>
As you can see, we’ve our DrawerLayout
root view and then its first child is a RelativeLayout
that represents the main content UI whereas the second child represents the navigation drawer. The navigation drawer first contains a box to represent the profile details followed by a ListView that’ll display a list of different sections that the user can visit (Home, Preferences, About).
The ViewGroup representing the main content UI should be the first child inside the DrawerLayout as the ordering in XML defines the z-index and the drawer should overlay the content, not the other way round.
The XML code is enough to initialize a functional drawer. You can test your app in the emulator or device to start swiping your finger from the left to draw the hamburger menu. However, we’ve some more work to do like initializing the list and adding onclick events on the list items.
Populating the Drawer List
This is the first step, we’ll initialize the ListView inside the Drawer with several options. Each list option will be represented by a custom NavItem
class object. So create this inner class (of the MainActivity
):
class NavItem { String mTitle; String mSubtitle; int mIcon; public NavItem(String title, String subtitle, int icon) { mTitle = title; mSubtitle = subtitle; mIcon = icon; } }
Next we need another inner class that’ll be a simple BaseAdapter
implementation that we’ll bind to the ListView for the sake of population:
class DrawerListAdapter extends BaseAdapter { Context mContext; ArrayList<NavItem> mNavItems; public DrawerListAdapter(Context context, ArrayList<NavItem> navItems) { mContext = context; mNavItems = navItems; } @Override public int getCount() { return mNavItems.size(); } @Override public Object getItem(int position) { return mNavItems.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; if (convertView == null) { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.drawer_item, null); } else { view = convertView; } TextView titleView = (TextView) view.findViewById(R.id.title); TextView subtitleView = (TextView) view.findViewById(R.id.subTitle); ImageView iconView = (ImageView) view.findViewById(R.id.icon); titleView.setText( mNavItems.get(position).mTitle ); subtitleView.setText( mNavItems.get(position).mSubtitle ); iconView.setImageResource(mNavItems.get(position).mIcon); return view; } }
We inflated R.layout.drawer_item
, so let’s create res/layout/drawer_item.xml
now:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="10dp" android:paddingBottom="10dp"> <ImageView android:id="@+id/icon" android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/ic_action_home" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginRight="20dp" android:layout_marginLeft="10dp" android:layout_marginTop="5dp" /> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:gravity="center_vertical" android:textColor="#000" android:text="Line 1" android:textStyle="bold" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/icon" android:layout_toEndOf="@+id/icon" /> <TextView android:id="@+id/subTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Line 2" android:layout_toRightOf="@+id/icon" android:layout_below="@+id/title" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" /> </RelativeLayout>
Now, we’ll set the adapter for the ListView inside onCreate()
of our MainActivity
:
private static String TAG = MainActivity.class.getSimpleName(); ListView mDrawerList; RelativeLayout mDrawerPane; private ActionBarDrawerToggle mDrawerToggle; private DrawerLayout mDrawerLayout; ArrayList<NavItem> mNavItems = new ArrayList<NavItem>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mNavItems.add(new NavItem("Home", "Meetup destination", R.drawable.ic_action_home)); mNavItems.add(new NavItem("Preferences", "Change your preferences", R.drawable.ic_action_settings)); mNavItems.add(new NavItem("About", "Get to know about us", R.drawable.ic_action_about)); // DrawerLayout mDrawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout); // Populate the Navigtion Drawer with options mDrawerPane = (RelativeLayout) findViewById(R.id.drawerPane); mDrawerList = (ListView) findViewById(R.id.navList); DrawerListAdapter adapter = new DrawerListAdapter(this, mNavItems); mDrawerList.setAdapter(adapter); // Drawer Item click listeners mDrawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItemFromDrawer(position); } }); }
Our ListView will be populated now so you should see it in the navigation drawer but it’s time to write code for what will happen once a list item is clicked, which will be defined by the selectItemFromDrawer()
Activity method which is what we called inside the onItemClick()
method of the OnItemClickListener
interface.
So when a list item is clicked, we could do several things. Since they’re different sections, we could just open different Activities using intents, insert a different Fragment for every list item into the main content view or even show dialogs. In this case we’ll keep it simple by creating a fragment called PreferencesFragment
and inserting it whenever an option is selected. Although you might want to create one fragment for each option and based on the position of the clicked item, insert its respective fragement. Let’s get into some code now.
/* * Called when a particular item from the navigation drawer * is selected. * */ private void selectItemFromDrawer(int position) { Fragment fragment = new PreferencesFragment(); FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.mainContent, fragment) .commit(); mDrawerList.setItemChecked(position, true); setTitle(mNavItems.get(position).mTitle); // Close the drawer mDrawerLayout.closeDrawer(mDrawerPane); }
This is how PreferencesFragement.java
looks like:
public class PreferencesFragment extends Fragment { public PreferencesFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_preferences, container, false); } }
and here’s the res/layout/fragment_preferences.xml
:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.looper.loop.PreferencesFragment"> <!-- TODO: Update blank fragment layout --> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/hello_blank_fragment" /> </FrameLayout>
It’s time to test your app in the emulator or your device itself. You’ll see a nice navigation drawer when you swipe right from the left edge with the profile box and the list in which you can select various options which’ll reflect in the action bar title because of this piece of code inside selectItemFromDrawer()
:
setTitle(mNavItems.get(position).mTitle);
Observe Open/Close Events of the Drawer
You can listen for the drawer’s open and close events by implementing DrawerLayout.DrawerListener
whose onDrawerOpened()
and onDrawerClosed()
will be called. But if you’re making use of the action bar, then extending ActionBarDrawerToggle
is a better idea. The ActionBarDrawerToggle
implements DrawerLayout.DrawerListener
, so you’ll have access to those callbacks but apart from that it also makes sure that the state of both the action bar icon (we’ll see how to add that in a bit) and the drawer is synced. Let’s see some code now.
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) { @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); invalidateOptionsMenu(); } @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); Log.d(TAG, "onDrawerClosed: " + getTitle()); invalidateOptionsMenu(); } }; mDrawerLayout.setDrawerListener(mDrawerToggle);
The four arguments passed to the ActionBarDrawerToggle
constructor are:
-
this
– Activity object containing the drawer with an ActionBar. -
mDrawerLayout
– The DrawerLayout to link to the Activity’s ActionBar. -
R.string.drawer_open
– String resource to describe the “open drawer” action for accessibility purpose. -
R.string.drawer_close
– String resource to describe the “close drawer” action for accessibility purpose.
There’s another version of this constructor that you should use when not using an ActionBar but a ToolBar.
You’ll notice in the callbacks we called invalidateOptionsMenu()
. This method declares that the option menu has changed and hence should be recreated invoking onPrepareOptionsMenu()
. So basically you might want to override onPrepareOptionsMenu()
to get rid of options from your overflow menu that are irrelevant to your navigation drawer.
// Called when invalidateOptionsMenu() is invoked public boolean onPrepareOptionsMenu(Menu menu) { // If the nav drawer is open, hide action items related to the content view boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); menu.findItem(R.id.action_search).setVisible(!drawerOpen); return super.onPrepareOptionsMenu(menu); }
Showing Up the Hamburger Menu Icon/Indicator
According to the design guidelines, apart from the touch gesture, when the drawer is closed the user should be able to open it by touching the navigation drawer indicator (those 3 lines) in the action bar. So now we’ll see how to display that special icon.
First, add this piece of code inside onCreate()
:
// More info: http://codetheory.in/difference-between-setdisplayhomeasupenabled-sethomebuttonenabled-and-setdisplayshowhomeenabled/ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
This piece of code will start showing up an arrow which is generally used for “up” (ancestral) navigation. Now when the user taps on this, onOptionsItemSelected()
will be called where you can delegate the event to the ActionBarDrawerToggle
.
@Override public boolean onOptionsItemSelected(MenuItem item) { // Pass the event to ActionBarDrawerToggle // If it returns true, then it has handled // the nav drawer indicator touch event if (mDrawerToggle.onOptionsItemSelected(item)) { return true; } // Handle your other action bar items... return super.onOptionsItemSelected(item); }
Now you’ll see the back arrow on which you can tap to open/close the drawer. But it won’t change to the 3 bar indicator icon when the drawer is closed. So in order to do that we’ve to override the onPostCreate()
method of the Activity where we’ll call the syncState()
method of the ActionBarDrawerToggle
. This method syncs the state of the indicator with the drawer. So when the drawer is closed it’ll show the correct indicator (indicating tap to open) while when the drawer is open it’ll show the back arrow indicator (indicating tap to close).
@Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); mDrawerToggle.syncState(); }
With that, its time to test your app that is baked with a navigation drawer now.
Conclusion
So we went through how to use the DrawerLayout
ViewGroup to create a navigation drawer and then populate that with a list of navigation options. Then we bounded click events to the list items that would replace the main content UI with a particular fragment. We also learnt how to listen to close/open events and show a navigation drawer indicator while using an action bar. You can also definitely make use of a Toolbar in place of an action bar and bind that to ActionBarDrawerToggle.
I don’t think by default the DrawerLayout supports sliding over the action bar which is what the material design guideline specify. So doing that might be a good exercise for you along with using custom images for the drawer icon/indicator in the action bar. If you give any of them a shot, then feel free to share your code in the comments section below!
Hi, i was trying to implement your solution but i got an error on this line “view = inflater.inflate(R.layout.drawer_item, null);”. Cannot resolve R.layout.drawer_item
Do you have any idea?, by the way pretty good article!!
Hey Luis,
Thanks for pointing that out. I’ve added the code for
res/layout/drawer_item.xml
. Just create that file and shove in the code and you’ll be good to go 🙂Cheers!
where is the source code??
Please follow the article, the source is broken up into pieces to make sure one understands how it all works together.
Hi,i’ve got this error when i try to execute…
java.lang.ClassCastException: android.widget.RelativeLayout$LayoutParams cannot be cast to android.support.v4.widget.DrawerLayout$LayoutParams
How can i solve?
Thanks 🙂
Ok solved…i ‘ve done a mistake!
I’ve put mDrawerList instead of mDrawerPane …now it works 🙂 …maybe u have to correct ur code 🙂
How ever thanks for ur example!
public boolean onPrepareOptionsMenu(Menu menu) {
// If the nav drawer is open, hide action items related to the content view
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
menu.findItem(R.id.action_settings).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
There is another thing that is incorrect
mDrawerToggle miss a parameter,the icon from resource.drawable…now is complete 🙂
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,R.drawable.abc_ic_go, R.string.hello_world, R.string.app_name) {…}
Getting a null pointer exception. What to do..??
Can we have whole code for MainActivity..??
Pastebin your code and let me know where exactly do you get the NullPointer Exception ? If you follow the article step by step you probably shouldn’t get that Exception.
did it , mR8Jm6px
Hi, in the last part of my MainActivity, i add the code as you describe in the tutorial, but i get this error: Error:(154, 70) error: incompatible types: PreferencesFragment cannot be converted to Fragment in the line
Line: fragmentManager.beginTransaction().replace(R.id.mainContent, fragment).commit();
Can you please tell me if you know how to fix?
Regards
Brus
Hi,
Are you sure you created a
PreferencesFragment
class that extendsFragment
? If yes then I’m unable to understand why that error would be thrown. Maybe try StackOverflow and see if someone can help you out.Also note you could use multiple fragments instead of just one
PreferencesFragment
for different items in the list. I used only once just for the sake of simplicity as my main objective is to show how to build the navigation drawer. Also based on the position you could set some Bundle data/arguments and then set layouts accordingly in the Fragment’sonCreateView
method.Hey . Thank u for the guide it was useful
There is a very one thing that I wanna know
how is it possible to open the sliding menu by tapping the menu button on the phone?
would u pls help me with it?
thnx in advance
hey, i want to a little discuss wiht you. i follow your tutorial.
i adding some component in activity_main. why component in activity)main visible when i show fagment activity ?
thanks before, and sorry for my bad englis 😀
Hi Sir Could you please share your Code?