Sunday 18 May 2014

Implementing Android Navigation Drawer to multiple activities

If you have come here, you are facing the problem of applying Navigation Drawer to more than one Activity in an easy way. You can find many tutorials about using Navigation Drawer in an Activity. But you are not going to repeat the same long process for another Activity, are you??

The trick is to create a BaseActivity which will be extended by each Activity that wants a Navigation Drawer.

Note- I have used ActionBarCompat so the Drawer will work on API>=7. You will have to add android-support-v7-appcompat as a libary into your project. If you don't want backwards  compatibility you don't have to do it. 

Here is the step by step process:-

1. Create the resources for Navigation Drawer


I have added the resources for Navigation Drawer's icons and names in res->strings.xml. We will use these resources  later.

<string-array name="nav_drawer_items">
        <item>First</item>
        <item>Second</item>
        <item>Third</item>
        <item>Fourth</item>
        <item>Fifth</item>
        <item>Sixth</item>
    </string-array>
<array name="nav_drawer_icons">  

<item>@drawable/ic_launcher</item>  
<item>@drawable/ic_launcher</item>  
<item>@drawable/ic_launcher</item>  
<item>@drawable/ic_launcher</item>
<item>@drawable/ic_launcher</item>  
<item>@drawable/ic_launcher</item>
</array>


2. Create a layout for BaseActivity


Create a layout res->drawer and paste the following code.

<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 -->
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
        <!-- Add content here -->
    </FrameLayout>

    <!-- The navigation drawer -->
    <ListView android:id="@+id/left_drawer"
        android:layout_width="260dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#1a1c25"/>
</android.support.v4.widget.DrawerLayout>

The main content view (the FrameLayout above) must be the first child in the DrawerLayout because the XML order implies z-ordering and the drawer must be on top of the content. 


3. Create a custom List Adapter for ListView


I have created a layout res->drawer_list_item for each row in the ListView. I have only used an ImageView and TextView for icon and text. You can tweak it according to your needs. Paste the following code.

<?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="48dp"
    android:background="@drawable/selector_navigation_drawer">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="25dp"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="12dp"
        android:layout_marginRight="12dp"
        android:src="@drawable/ic_launcher"
        android:layout_centerVertical="true" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_toRightOf="@id/icon"
        android:text="no text"
        android:textColor="#fff"
        android:layout_centerVertical="true"
        android:gravity="center_vertical"
        android:paddingRight="40dp"/>


</RelativeLayout>


Now create a file NavDrawerItem.java in your project's src folder and paste the following code.


package com.nadeem.nav;

public class NavDrawerItem {
private String title;
private int icon;
public NavDrawerItem() {
}

public NavDrawerItem(String title, int icon) {
this.title = title;
this.icon = icon;
}
public NavDrawerItem(String title) {
this.title = title;
}

public String getTitle() {
return this.title;
}

public int getIcon() {
return this.icon;
}

public void setTitle(String title) {
this.title = title;
}

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

}

Now create the List Adapter NavDrawerListAdapter.java in your project's src folder. Paste the following code.


package com.nadeem.nav;

import java.util.ArrayList;



import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class NavDrawerListAdapter extends BaseAdapter {
     
    private Context context;
    private ArrayList<NavDrawerItem> navDrawerItems;
     
    public NavDrawerListAdapter(Context context, ArrayList<NavDrawerItem> navDrawerItems){
        this.context = context;
        this.navDrawerItems = navDrawerItems;
    }

    @Override
    public int getCount() {
        return navDrawerItems.size();
    }

    @Override
    public Object getItem(int position) {       
        return navDrawerItems.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            LayoutInflater mInflater = (LayoutInflater)
                    context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
            convertView = mInflater.inflate(R.layout.drawer_list_item, null);
        }
          
        ImageView imgIcon = (ImageView) convertView.findViewById(R.id.icon);
        TextView txtTitle = (TextView) convertView.findViewById(R.id.title);
        
          
        imgIcon.setImageResource(navDrawerItems.get(position).getIcon());        
        txtTitle.setText(navDrawerItems.get(position).getTitle());
         
       
         
        return convertView;
    }

}

Now,we have created all the required files. It's time to create our BaseActivity class.



4.  Creating BaseActivity.java


Paste the following code.

public class BaseActivity extends ActionBarActivity {
private DrawerLayout mDrawerLayout;
private ListView mDrawerList;
private ActionBarDrawerToggle mDrawerToggle;
protected RelativeLayout _completeLayout, _activityLayout;
// nav drawer title
private CharSequence mDrawerTitle;

// used to store app title
private CharSequence mTitle;

private ArrayList<NavDrawerItem> navDrawerItems;
private NavDrawerListAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawer);
// if (savedInstanceState == null) {
// // on first time display view for first nav item
// // displayView(0);
// }
}

public void set(String[] navMenuTitles,TypedArray navMenuIcons) {
mTitle = mDrawerTitle = getTitle();

mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.left_drawer);

navDrawerItems = new ArrayList<NavDrawerItem>();

// adding nav drawer items
if(navMenuIcons==null){
for(int i=0;i<navMenuTitles.length;i++){
navDrawerItems.add(new NavDrawerItem(navMenuTitles[i]));
}}else{
for(int i=0;i<navMenuTitles.length;i++){
navDrawerItems.add(new NavDrawerItem(navMenuTitles[i],navMenuIcons.getResourceId(i, -1)));
}
}

mDrawerList.setOnItemClickListener(new SlideMenuClickListener());

// setting the nav drawer list adapter
adapter = new NavDrawerListAdapter(getApplicationContext(),
navDrawerItems);
mDrawerList.setAdapter(adapter);

// enabling action bar app icon and behaving it as toggle button
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
// getSupportActionBar().setIcon(R.drawable.ic_drawer);

mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
R.drawable.ic_drawer, // nav menu toggle icon
R.string.app_name, // nav drawer open - description for
// accessibility
R.string.app_name // nav drawer close - description for
// accessibility
) {
public void onDrawerClosed(View view) {
getSupportActionBar().setTitle(mTitle);
// calling onPrepareOptionsMenu() to show action bar icons
supportInvalidateOptionsMenu();
}

public void onDrawerOpened(View drawerView) {
getSupportActionBar().setTitle(mDrawerTitle);
// calling onPrepareOptionsMenu() to hide action bar icons
supportInvalidateOptionsMenu();
}
};
mDrawerLayout.setDrawerListener(mDrawerToggle);

}

Above, we declare a method set(...), which we will call in different activities using Navigation Drawer. 
When the user selects an item in the drawer's list, the system calls onItemClick() on the OnItemClickListener given to setOnItemClickListener().
What you do in the onItemClick() method depends on how you've implemented your app structure. In the following code, selecting each item in the list calls  a diffferent Activity into the main content view. You can also insert different Fragments, according to your needs.
In your BaseActivity.java paste the following code-
private class SlideMenuClickListener implements
ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// display view for selected nav drawer item
displayView(position);
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// getSupportMenuInflater().inflate(R.menu.main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

if (item.getItemId() == android.R.id.home) {
if (mDrawerLayout.isDrawerOpen(mDrawerList)) {
mDrawerLayout.closeDrawer(mDrawerList);
} else {
mDrawerLayout.openDrawer(mDrawerList);
}
}

return super.onOptionsItemSelected(item);
}

/***
* Called when invalidateOptionsMenu() is triggered
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// if nav drawer is opened, hide the action items
// boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
// menu.findItem(R.id.action_settings).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}

/**
* Diplaying fragment view for selected nav drawer list item
* */
private void displayView(int position) {
// update the main content by replacing fragments
switch (position) {
case 0:
Intent intent = new Intent(this, First.class);
startActivity(intent);
finish();
break;
case 1:
Intent intent1 = new Intent(this, Second.class);
startActivity(intent1);
finish();
break;
case 2:
Intent intent2 = new Intent(this, third.class);
startActivity(intent2);
finish();
break;
case 3:
Intent intent3 = new Intent(this, fourth.class);
startActivity(intent3);
finish();
break;
case 4:
Intent intent4 = new Intent(this, fifth.class);
startActivity(intent4);
finish();
break;
case 5:
Intent intent5 = new Intent(this, sixth.class);
startActivity(intent5);
finish();
break;
default:
break;
}

// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
mDrawerList.setSelection(position);
mDrawerLayout.closeDrawer(mDrawerList);
}

@Override
public void setTitle(CharSequence title) {
mTitle = title;
getActionBar().setTitle(mTitle);
}

/**
* When using the ActionBarDrawerToggle, you must call it during
* onPostCreate() and onConfigurationChanged()...
*/

@Override
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);
// Pass any configuration change to the drawer toggls
mDrawerToggle.onConfigurationChanged(newConfig);
}
}


5. Use it


Now everything is set. Let's say we want to use the Navigation Drawer in first.java class. We extend BaseActivity instead of normal Activity or ActionBarActivity. We add the following code in the class-

private String[] navMenuTitles;
private TypedArray navMenuIcons;
navMenuTitles = getResources().getStringArray(R.array.nav_drawer_items); // load titles from strings.xml
navMenuIcons = getResources()
                .obtainTypedArray(R.array.nav_drawer_icons);//load icons from strings.xml

set(navMenuTitles,navMenuIcons);

The above code provides with the flexibility of changing icon and text for every Activity. You just have to pass the Titles and Icons in the set(...) method and the BaseActivity will handle the rest.

The last thing we have to do is to change the layout for first.java. We create a layout res->first.

We only use a TextView as our main content view to maintain simplicity. Here is how we do it-

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

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

        <TextView
                        android:id="@+id/textView1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentTop="true"
                        android:layout_centerHorizontal="true"
                        android:layout_marginTop="14dp"
                        android:text="Hey there!!"
                        android:textAppearance="?android:attr/textAppearanceLarge"
                        android:textColor="#ffffff"
                        android:textStyle="bold" />
    </FrameLayout>

    <!-- The navigation drawer -->

    <ListView
        android:id="@+id/left_drawer"
        android:layout_width="260dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="#1a1c25"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp" />


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

You must notice that the main content that we wanted to display has been placed inside the FrameLayout. This is the trick. Every time you want to use a different content you just have to place the code inside that FrameLayout only. Let's take another example. If we want to use an ImageView as our layout. The code will be-

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

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

        <ImageView
        android:id="@+id/start2"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@drawable/abc" />
    </FrameLayout>

    <!-- The navigation drawer -->

    <ListView
        android:id="@+id/left_drawer"
        android:layout_width="260dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="#1a1c25"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp" />


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


That's it. We have successfully implemented Navigation Drawer in multiple activities. To use it you just have to repeat step 5 and bam!! you are good to go.


Full source code is available here.

Hope you liked it as much as I enjoyed it writing..