Creating a Navigation Drawer

In the latest I/O 2013, Google added a new pattern: Drawer Navigation.
I think Google did really good job.
You can find official design guidelines here.

Implementing drawer navigation is simple. The components required are now included in the latest support library (release 13).

The first thing that we need to do is modify our layout and insert a DrawerLayout.

<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" >

    <!-- The main content view -->

    <RelativeLayout
        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"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context=".MainActivity" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/drawer_text" />
    </RelativeLayout>

    <!-- The navigation drawer -->

    <ListView
        android:id="@+id/drawer"
        android:layout_width="320dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="#F3F3F4"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp" />

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

It is important to note:
  • The main content view (the RelativeLayout above) must be the first child
  • The width of the navigation drawer should be between a minimum of 240 dp and a maximum of 320 dp
  • The height of the navigation drawer should be match_parent
  • The drawer view (the ListView) must specify its horizontal gravity with the android:layout_gravity

It is enough to get a empty Navigation Drawer working.
If we swipe from the left hand edge of the screen our Navigation Drawer appears.

Now we can populate our List with a simple Adapter.

   @Override
   protected void onCreate(Bundle savedInstanceState) {
 
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main_drawer);
       _initMenu();

   }

   private void _initMenu() {
       NsMenuAdapter mAdapter = new NsMenuAdapter(this);

        // Add Header
        mAdapter.addHeader(R.string.ns_menu_main_header);
        .....
        mAdapter.addItem(mItem);
 
 
        mDrawerList = (ListView) findViewById(R.id.drawer);
        if (mDrawerList != null)
            mDrawerList.setAdapter(mAdapter);
   
  }
With a very simple code, we have our drawer.


To handle click on menu item:
   private void _initMenu() {
      .....
      mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
   }

   private class DrawerItemClickListener implements ListView.OnItemClickListener {

       @Override
       public void onItemClick(AdapterView parent, View view, int position,long id) {
         
          // Highlight the selected item, update the title, and close the drawer
          // update selected item and title, then close the drawer
          mDrawerList.setItemChecked(position, true);
          setTitle("......");

          String text= "menu click... should be implemented";
          Toast.makeText(MainActivity.this, text , Toast.LENGTH_LONG).show();
          mDrawer.closeDrawer(mDrawerList);
   
       }
  }
To listen for drawer open and close events, and use new drawer navigation icon, we should use the ActionBarDrawerToggle class:
     @Override
     protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main_drawer);


          // enable ActionBar app icon to behave as action to toggle nav drawer
          getActionBar().setDisplayHomeAsUpEnabled(true);
          getActionBar().setHomeButtonEnabled(true);

          mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
          mDrawerToggle = new CustomActionBarDrawerToggle(this, mDrawer);
          mDrawer.setDrawerListener(mDrawerToggle);

    }

    private class CustomActionBarDrawerToggle extends ActionBarDrawerToggle {

          public CustomActionBarDrawerToggle(Activity mActivity,DrawerLayout mDrawerLayout){
                super(
                      mActivity,               /* host Activity */
                      mDrawerLayout,           /* DrawerLayout object */
                      R.drawable.ic_drawer,    /* nav drawer icon to replace 'Up' caret */
                      R.string.ns_menu_open,   /* "open drawer" description */
                      R.string.ns_menu_close); /* "close drawer" description */
          }


          /** Called when a drawer has settled in a completely closed state. */
          @Override
          public void onDrawerClosed(View view) {
               getActionBar().setTitle(getString(R.string.ns_menu_close));
          }

          /** Called when a drawer has settled in a completely open state. */
          @Override
          public void onDrawerOpened(View drawerView) {
               getActionBar().setTitle(getString(R.string.ns_menu_open));
          }
    }
The drawer navigation implementation page provides a link to a zip file for the default drawer navigation icon in both light and dark holo theme.

To handle the app icon touch event you can use:
   @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Pass the event to ActionBarDrawerToggle, if it returns
        // true, then it has handled the app icon touch event
        if (mDrawerToggle.onOptionsItemSelected(item)) {
          return true;
        }
        // Handle your other action bar items...

        return super.onOptionsItemSelected(item);
    }
To work properly you have to insert also this code:
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }
It is very important, manage icons on action bar.
When the user expands the navigation drawer, you should remove actions from the action bar that are contextual to the underlying view.
You can use our ActionBarDrawerToggle:
    private class CustomActionBarDrawerToggle extends ActionBarDrawerToggle {

        @Override
        public void onDrawerClosed(View view) {
            invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()  
        }

        @Override
        public void onDrawerOpened(View drawerView) {
            invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
        }
    }

    /* Called whenever we call invalidateOptionsMenu() */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        // If the nav drawer is open, hide action items related to the content view
        boolean drawerOpen = mDrawer.isDrawerOpen(mDrawerList);
        menu.findItem(R.id.action_save).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }

Finally if we want add a shadow:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
         // set a custom shadow that overlays the main content when the drawer opens
         mDrawer.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
    }


You can get code from GitHub:

Comments

Popular posts from this blog

AntiPattern: freezing a UI with Broadcast Receiver

NotificationListenerService and kitkat

How to centralize the support libraries dependencies in gradle