Android : Navigation Drawer

A few days ago, i wrote a tutorial about creating a sliding menu using the SlidingMenu library. Since, Google has introduced the NavigationDrawer in the android support library. This tutorial shows you how to use it.



Test before
If you wish to test before, you can install the application with the following link. The whole source code of the project is also available on github.

logo-app
Your app idea
Michenux
Free   
pulsante-google-play-store
pulsante-appbrain
qrcode-app

Main layout

This is the layout of the activity (file res/layout/main.xml). Notice the root object is a DrawerLayout. You will use the first child (FrameLayout) to display your app contents. The second child is the sliding menu and is a ListView.

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/drawer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >

    <FrameLayout
       android:id="@+id/content_frame"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />

    <ListView
       android:id="@+id/left_drawer"
       android:layout_width="240dp"
       android:layout_height="match_parent"
       android:layout_gravity="start"
       android:background="@color/purple_dark"
       android:choiceMode="singleChoice"
       android:divider="@android:color/darker_gray"
       android:dividerHeight="0.1dp"
       android:listSelector="@drawable/navdrawer_listselector"/>

</android.support.v4.widget.DrawerLayout>

Describe the menu

The ListView will contains two kinds of objects : sections and section items. Here’s the classes to describe your menu.

NavDrawerItem is the common interface for element types of the menu. The method isEnabled() tells if the item is touchable or not (a section is not).
The method updateActionBarTitle() indicates if the title of the action bar must be updated when the user selects a menu item.

public interface NavDrawerItem {
    public int getId();
    public String getLabel();
    public int getType();
    public boolean isEnabled();
    public boolean updateActionBarTitle();
}

The class to describe a section of the menu :

public class NavMenuSection implements NavDrawerItem {

    public static final int SECTION_TYPE = 0;
    private int id;
    private String label;

    private NavMenuSection() {
    }
   
    public static NavMenuSection create( int id, String label ) {
        NavMenuSection section = new NavMenuSection();
        section.setLabel(label);
        return section;
    }
   
    @Override
    public int getType() {
        return SECTION_TYPE;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public boolean updateActionBarTitle() {
        return false;
    }
}

The class to describe a section item of the menu :

public class NavMenuItem implements NavDrawerItem {

    public static final int ITEM_TYPE = 1 ;

    private int id ;
    private String label ; 
    private int icon ;
    private boolean updateActionBarTitle ;

    private NavMenuItem() {
    }

    public static NavMenuItem create( int id, String label, String icon, boolean updateActionBarTitle, Context context ) {
        NavMenuItem item = new NavMenuItem();
        item.setId(id);
        item.setLabel(label);
        item.setIcon(context.getResources().getIdentifier( icon, "drawable", context.getPackageName()));
        item.setUpdateActionBarTitle(updateActionBarTitle);
        return item;
    }
   
    @Override
    public int getType() {
        return ITEM_TYPE;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public int getIcon() {
        return icon;
    }

    public void setIcon(int icon) {
        this.icon = icon;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public boolean updateActionBarTitle() {
        return this.updateActionBarTitle;
    }

    public void setUpdateActionBarTitle(boolean updateActionBarTitle) {
        this.updateActionBarTitle = updateActionBarTitle;
    }
}

Layouts for sections and items

Here’s the layout for sections (file res/layout/navdrawer_section.xml). In this tutorial, a section holds only a title.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:background="@color/purple_dark">
   
    <TextView
       android:id="@+id/navmenusection_label"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:text="Section"
       android:textSize="18sp"
       android:paddingTop="7dp"
       android:paddingBottom="7dp"
       android:textColor="#FFFFFF"
       style="?android:attr/listSeparatorTextViewStyle"
       android:background="@color/purple_dark"/>

</RelativeLayout>

Here’s the layout for menu items (file res/layout/navdrawer_item.xml). In this tutorial, a menu item holds a icon and a title.

<?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="wrap_content"
   android:background="?android:attr/activatedBackgroundIndicator"
   android:textAppearance="?android:attr/textAppearanceListItemSmall"
   android:gravity="center_vertical"
   android:paddingRight="10dp"
   android:minHeight="?android:attr/listPreferredItemHeightSmall"
   >

    <ImageView
       android:id="@+id/navmenuitem_icon"
       android:layout_width="wrap_content"
       android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:paddingLeft="15dp"/>

    <TextView
       android:id="@+id/navmenuitem_label"
       android:layout_width="match_parent"
       android:layout_height="50dp"
       android:layout_toRightOf="@id/navmenuitem_icon"
       android:gravity="center_vertical"
       android:text="TextView"
       android:textColor="#FFFFFF"/>

</RelativeLayout>

Adapter for the list

Below, the adapter to use with the ListView of the menu. It can handle both NavMenuSection and NavMenuItem classes.

public class NavDrawerAdapter extends ArrayAdapter<NavDrawerItem> {

    private LayoutInflater inflater;
   
    public NavDrawerAdapter(Context context, int textViewResourceId, NavDrawerItem[] objects ) {
        super(context, textViewResourceId, objects);
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = null ;
        NavDrawerItem menuItem = this.getItem(position);
        if ( menuItem.getType() == NavMenuItem.ITEM_TYPE ) {
            view = getItemView(convertView, parent, menuItem );
        }
        else {
            view = getSectionView(convertView, parent, menuItem);
        }
        return view ;
    }
   
    public View getItemView( View convertView, ViewGroup parentView, NavDrawerItem navDrawerItem ) {
       
        NavMenuItem menuItem = (NavMenuItem) navDrawerItem ;
        NavMenuItemHolder navMenuItemHolder = null;
       
        if (convertView == null) {
            convertView = inflater.inflate( R.layout.navdrawer_item, parentView, false);
            TextView labelView = (TextView) convertView
                    .findViewById( R.id.navmenuitem_label );
            ImageView iconView = (ImageView) convertView
                    .findViewById( R.id.navmenuitem_icon );

            navMenuItemHolder = new NavMenuItemHolder();
            navMenuItemHolder.labelView = labelView ;
            navMenuItemHolder.iconView = iconView ;

            convertView.setTag(navMenuItemHolder);
        }

        if ( navMenuItemHolder == null ) {
            navMenuItemHolder = (NavMenuItemHolder) convertView.getTag();
        }
                   
        navMenuItemHolder.labelView.setText(menuItem.getLabel());
        navMenuItemHolder.iconView.setImageResource(menuItem.getIcon());
       
        return convertView ;
    }

    public View getSectionView(View convertView, ViewGroup parentView,
            NavDrawerItem navDrawerItem) {
       
        NavMenuSection menuSection = (NavMenuSection) navDrawerItem ;
        NavMenuSectionHolder navMenuItemHolder = null;
       
        if (convertView == null) {
            convertView = inflater.inflate( R.layout.navdrawer_section, parentView, false);
            TextView labelView = (TextView) convertView
                    .findViewById( R.id.navmenusection_label );

            navMenuItemHolder = new NavMenuSectionHolder();
            navMenuItemHolder.labelView = labelView ;
            convertView.setTag(navMenuItemHolder);
        }

        if ( navMenuItemHolder == null ) {
            navMenuItemHolder = (NavMenuSectionHolder) convertView.getTag();
        }
                   
        navMenuItemHolder.labelView.setText(menuSection.getLabel());
       
        return convertView ;
    }
   
    @Override
    public int getViewTypeCount() {
        return 2;
    }
   
    @Override
    public int getItemViewType(int position) {
        return this.getItem(position).getType();
    }
   
    @Override
    public boolean isEnabled(int position) {
        return getItem(position).isEnabled();
    }
   
   
    private static class NavMenuItemHolder {
        private TextView labelView;
        private ImageView iconView;
    }
   
    private class NavMenuSectionHolder {
        private TextView labelView;
    }
}

AbstractNavDrawerActivity

There’s a lot of code to write in the activity to manage the Navigation Drawer. I have written a class to simplify the stuff. Just inherit your activity from this one. If you want more information how it works behind, i suggest you to read the android tutorial : http://developer.android.com/training/implementing-navigation/nav-drawer.html, most of the code below comes from this tutorial.

public abstract class AbstractNavDrawerActivity extends FragmentActivity {

    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
   
    private ListView mDrawerList;
   
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;
   
    private NavDrawerActivityConfiguration navConf ;
   
    protected abstract NavDrawerActivityConfiguration getNavDrawerConfiguration();
   
    protected abstract void onNavItemSelected( int id );
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        navConf = getNavDrawerConfiguration();
       
        setContentView(navConf.getMainLayout());
       
        mTitle = mDrawerTitle = getTitle();
       
        mDrawerLayout = (DrawerLayout) findViewById(navConf.getDrawerLayoutId());
        mDrawerList = (ListView) findViewById(navConf.getLeftDrawerId());
        mDrawerList.setAdapter(navConf.getBaseAdapter());
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
       
        this.initDrawerShadow();
       
        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
       
        mDrawerToggle = new ActionBarDrawerToggle(
                this,
                mDrawerLayout,
                getDrawerIcon(),
                navConf.getDrawerOpenDesc(),
                navConf.getDrawerCloseDesc()
                ) {
            public void onDrawerClosed(View view) {
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu();
            }

            public void onDrawerOpened(View drawerView) {
                getActionBar().setTitle(mDrawerTitle);
                invalidateOptionsMenu();
            }
        };
        mDrawerLayout.setDrawerListener(mDrawerToggle);
    }
   
    protected void initDrawerShadow() {
        mDrawerLayout.setDrawerShadow(navConf.getDrawerShadow(), GravityCompat.START);
    }
   
    protected int getDrawerIcon() {
        return R.drawable.ic_drawer;
    }
   
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }
   
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        if ( navConf.getActionMenuItemsToHideWhenDrawerOpen() != null ) {
            boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
            for( int iItem : navConf.getActionMenuItemsToHideWhenDrawerOpen()) {
                menu.findItem(iItem).setVisible(!drawerOpen);
            }
        }
        return super.onPrepareOptionsMenu(menu);
    }
   
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (mDrawerToggle.onOptionsItemSelected(item)) {
            return true;
        }
        else {
            return false;
        }
    }
   
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ( keyCode == KeyEvent.KEYCODE_MENU ) {
            if ( this.mDrawerLayout.isDrawerOpen(this.mDrawerList)) {
                this.mDrawerLayout.closeDrawer(this.mDrawerList);
            }
            else {
                this.mDrawerLayout.openDrawer(this.mDrawerList);
            }
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
   
    protected DrawerLayout getDrawerLayout() {
        return mDrawerLayout;
    }

    protected ActionBarDrawerToggle getDrawerToggle() {
        return mDrawerToggle;
    }
   
    private class DrawerItemClickListener implements ListView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            selectItem(position);
        }
    }
   
    public void selectItem(int position) {
        NavDrawerItem selectedItem = navConf.getNavItems()[position];
       
        this.onNavItemSelected(selectedItem.getId());
        mDrawerList.setItemChecked(position, true);
       
        if ( selectedItem.updateActionBarTitle()) {
            setTitle(selectedItem.getLabel());
        }
       
        if ( this.mDrawerLayout.isDrawerOpen(this.mDrawerList)) {
            mDrawerLayout.closeDrawer(mDrawerList);
        }
    }
   
    @Override
    public void setTitle(CharSequence title) {
        mTitle = title;
        getActionBar().setTitle(mTitle);
    }
}

NavDrawerActivityConfiguration

NavDrawerActivityConfiguration is the bean to configure the sliding menu and is needed by the AbstractNavDrawerActivity activity.

Here’s a short description of the properties :

public class NavDrawerActivityConfiguration {

    private int mainLayout;
    private int drawerShadow;
    private int drawerLayoutId;
    private int leftDrawerId;
    private int[] actionMenuItemsToHideWhenDrawerOpen;
    private NavDrawerItem[] navItems;
    private int drawerOpenDesc;
    private int drawerCloseDesc;
    private BaseAdapter baseAdapter;

    public int getMainLayout() {
        return mainLayout;
    }

    public void setMainLayout(int mainLayout) {
        this.mainLayout = mainLayout;
    }

    public int getDrawerShadow() {
        return drawerShadow;
    }

    public void setDrawerShadow(int drawerShadow) {
        this.drawerShadow = drawerShadow;
    }

    public int getDrawerLayoutId() {
        return drawerLayoutId;
    }

    public void setDrawerLayoutId(int drawerLayoutId) {
        this.drawerLayoutId = drawerLayoutId;
    }

    public int getLeftDrawerId() {
        return leftDrawerId;
    }

    public void setLeftDrawerId(int leftDrawerId) {
        this.leftDrawerId = leftDrawerId;
    }

    public int[] getActionMenuItemsToHideWhenDrawerOpen() {
        return actionMenuItemsToHideWhenDrawerOpen;
    }

    public void setActionMenuItemsToHideWhenDrawerOpen(
            int[] actionMenuItemsToHideWhenDrawerOpen) {
        this.actionMenuItemsToHideWhenDrawerOpen = actionMenuItemsToHideWhenDrawerOpen;
    }

    public NavDrawerItem[] getNavItems() {
        return navItems;
    }

    public void setNavItems(NavDrawerItem[] navItems) {
        this.navItems = navItems;
    }

    public int getDrawerOpenDesc() {
        return drawerOpenDesc;
    }

    public void setDrawerOpenDesc(int drawerOpenDesc) {
        this.drawerOpenDesc = drawerOpenDesc;
    }

    public int getDrawerCloseDesc() {
        return drawerCloseDesc;
    }

    public void setDrawerCloseDesc(int drawerCloseDesc) {
        this.drawerCloseDesc = drawerCloseDesc;
    }

    public BaseAdapter getBaseAdapter() {
        return baseAdapter;
    }

    public void setBaseAdapter(BaseAdapter baseAdapter) {
        this.baseAdapter = baseAdapter;
    }
}

Example of activity

This is an example of a subclass that inherits from AbstractNavDrawerActivity. Look at how the menu is described to make your own and
look at the method onNavItemSelected() to handle clicks on menu items.

public class YourAppMainActivity extends AbstractNavDrawerActivity {
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if ( savedInstanceState == null ) {
            getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, new MainFragment()).commit();
        }
    }
   
    @Override
    protected NavDrawerActivityConfiguration getNavDrawerConfiguration() {
       
        NavDrawerItem[] menu = new NavDrawerItem[] {
                NavMenuSection.create( 100, "Demos"),
                NavMenuItem.create(101,"List/Detail (Fragment)", "navdrawer_friends", false, this),
                NavMenuItem.create(102, "Airport (AsyncTask)", "navdrawer_airport", true, this),
                NavMenuSection.create(200, "General"),
                NavMenuItem.create(202, "Rate this app", "navdrawer_rating", false, this),
                NavMenuItem.create(203, "Eula", "navdrawer_eula", false, this),
                NavMenuItem.create(204, "Quit", "navdrawer_quit", false, this)};
       
        NavDrawerActivityConfiguration navDrawerActivityConfiguration = new NavDrawerActivityConfiguration();
        navDrawerActivityConfiguration.setMainLayout(R.layout.main);
        navDrawerActivityConfiguration.setDrawerLayoutId(R.id.drawer_layout);
        navDrawerActivityConfiguration.setLeftDrawerId(R.id.left_drawer);
        navDrawerActivityConfiguration.setNavItems(menu);
        navDrawerActivityConfiguration.setDrawerShadow(R.drawable.drawer_shadow);      
        navDrawerActivityConfiguration.setDrawerOpenDesc(R.string.drawer_open);
        navDrawerActivityConfiguration.setDrawerCloseDesc(R.string.drawer_close);
        navDrawerActivityConfiguration.setBaseAdapter(
            new NavDrawerAdapter(this, R.layout.navdrawer_item, menu ));
        return navDrawerActivityConfiguration;
    }
   
    @Override
    protected void onNavItemSelected(int id) {
        switch ((int)id) {
        case 101:
            getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, new FriendMainFragment()).commit();
            break;
        case 102:
            getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, new AirportFragment()).commit();
            break;
        }
    }
}

Drawables and resources

This drawable (res/drawable/listitem_background.xml) is used to configure the background color of the selected item.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_activated="true" android:drawable="@color/purple_middle" />
    <item android:drawable="@android:color/transparent" />
</selector>

This one is used to configure the background color when a menu item is pressed navdrawer_listselector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
  android:exitFadeDuration="@android:integer/config_mediumAnimTime">

   <item android:drawable="@color/purple_light" android:state_pressed="true"/>
   <item android:drawable="@color/purple_light" android:state_selected="true"/>
   <item android:drawable="@color/purple_light" android:state_activated="true"/>

</selector>

In your application theme, add the following property :

<item name="android:activatedBackgroundIndicator">@drawable/listitem_background</item>

My color file (res/values/color.xml):

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">      
    <color name="purple_light">#a276eb</color>
    <color name="purple_middle">#8658ce</color>
    <color name="purple_dark">#6a3ab2</color>
</resources>

Add the following strings in your string file (res/values/strings.xml)

<string name="drawer_open">Open navigation drawer</string>
<string name="drawer_close">Close navigation drawer</string>

Get the missing drawables with the following links :

Finally
I hope you find this tutorial useful. Don’t hesitate to communicate your customizations/optimizations so i can improve this tutorial.

Share Button

Comments

Leave a Reply