In this blog post ,I will show a walk-through on how to create a custom Navigation Drawer using the auto generated Android Studio Navigation Drawer template. This also applies if you are working with the ADT plugin in Eclipse. Using the auto generated Android Studio Navigation Drawer gives you are great head start for most of the use cases of Navigation Drawer; however it can also leave you in the middle of nowhere if you want to apply some customization. Here is what our demo app will look like when completed.
<iframe class='youtube-player' type='text/html' width='960' height='570' src='http://www.youtube.com/embed/uIqM8WLcdFw?version=3&rel=1&fs=1&showsearch=0&showinfo=1&iv_load_policy=1&wmode=transparent' frameborder='0' allowfullscreen='true'></iframe>
Project Creation
If you want to follow along, go ahead and create a new Android project using Android Studio, I have named my project NavigationDrawerExample. Select the new project template, select Phone and Tablet as your target devices and choose any API level you want to target for this project I selected API level 16. Click next and select Activity with Navigation like the image below:
Click next and feel free to leave the default name of MainActivity, we are only going to be using one Activity for this demo. Once the project is created, you will notice one little departure from the former template which is that the Navigation Drawer boiler plate codes are now defined in a new Fragment called NavigationDrawerFragment instead of inside the MainActivity. And out of the box, this implementation provides the core functions of a Navigation Drawer including opening up on first use and listening to see if the user has learned about the Navigation drawer for the first time so that it will stop showing on start up. It handles switching the title to show the currently selected Fragment.
What is missing?
- First there is this embedded Fragment called PlaceHolderFragment that seems to confuse some people
- How do we add more Fragments to the Navigation Drawer list
- How do we add images to the Navigation Drawer list
- How do we navigate to lower level Fragments that are not represented in the Navigation Drawer list
- How do we swtich between the Navigation Drawer icon (hamburger) and Actionbar Up caret button
- How do we update the title of the Actionbar to show titles of lower level Fragments
Step 1 – Add Fragments
Go ahead and add all of your fragments if you know them ahead. For this demo project, I have added the following Fragments in a Package I called Fragments. No functionality is actually implemented in these fragments, they are used as demo fragment to demonstrate Navigation Drawer
- CustomerListFragment
- CustomerDetailFragment
- ProductListFragment
- ProductDetailFragment
- OrderListFragment
- OrderDetailsFragment
Step 2: Add DrawerItem
This is not required but very very helpful. Each item in our improved Navigation Drawer list will have two elements, an image (icon) and a text which is the name of the Fragment we want to go to. So we will define a Java class that will hold exactly those two elements and you will see its usage as we proceed. So please go ahead and create another Package called Model and add a Java class called DrawerItem.java with the content below:
public class DrawerItem {
String ItemName;
int imgResID;
public DrawerItem(String itemName, int imgResID) {
super();
ItemName = itemName;
this.imgResID = imgResID;
}
public String getItemName() {
return ItemName;
}
public void setItemName(String itemName) {
ItemName = itemName;
}
public int getImgResID() {
return imgResID;
}
public void setImgResID(int imgResID) {
this.imgResID = imgResID;
}
}
If you are following along, then your project structure should look like the image below:
Step 3 – Add Navigation Drawer List Icons
If you want your Navigation Drawer list items to have icons to their right, go ahead and add those icons to the drawables now. If you do not have existing icons save yourself some time and head over to Roman Nurik Android Asset Studio and generate the needed icons. That is exactly what I did for this demo project. Copy those icons over to your drawables folder. When you download the icons they will be in zip files, unzip the files and drop the drawable folders to your res folder, Android Studio will make sure that that each image go to their respective folders hdpi, mdpi, etc.
Step 4 – Create a new Adapter
If you look at the NavigationFragment, you will notice that it uses an inline Adapter to manage the Navigation Drawer and supplied a static list of String like so:
mDrawerListView.setAdapter(new ArrayAdapter(
getActionBar().getThemedContext(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1,
new String[]{
getString(R.string.title_section1),
getString(R.string.title_section2),
getString(R.string.title_section3),
}));
Since our improved Navigation Drawer list will have an image and a text we need to supply our own custom adapter. Before we create the custom adapter, we need to define how each row in that custom list will look like. So go to your layout folder in the res folder and add the following xml layout file. I have named mine custom_drawer_item.xml and here is the content:
<?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="fill_parent" >
<LinearLayout
android:id="@+id/itemLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:orientation="vertical"
android:layout_marginTop="0dp"
android:background="?android:attr/activatedBackgroundIndicator"
>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="55dp"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/drawer_icon"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/drawer_itemName"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
With the row layout defined, go ahead and create a Java class, I have named mine CustomerDrawerAdapter.java and placed it in an Adapter package. And here is the content of that adapter:
public class CustomDrawerAdapter extends ArrayAdapter {
Context context;
List drawerItemList;
int layoutResID;
public CustomDrawerAdapter(Context context, int layoutResourceID,
List listItems) {
super(context, layoutResourceID, listItems);
this.context = context;
this.drawerItemList = listItems;
this.layoutResID = layoutResourceID;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
DrawerItemHolder drawerHolder;
View view = convertView;
if (view == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
drawerHolder = new DrawerItemHolder();
view = inflater.inflate(layoutResID, parent, false);
drawerHolder.ItemName = (TextView) view
.findViewById(R.id.drawer_itemName);
drawerHolder.icon = (ImageView) view.findViewById(R.id.drawer_icon);
view.setTag(drawerHolder);
} else {
drawerHolder = (DrawerItemHolder) view.getTag();
}
DrawerItem dItem = (DrawerItem) this.drawerItemList.get(position);
drawerHolder.icon.setImageDrawable(view.getResources().getDrawable(
dItem.getImgResID()));
drawerHolder.ItemName.setText(dItem.getItemName());
return view;
}
private static class DrawerItemHolder {
TextView ItemName;
ImageView icon;
}
}
Step 5 – Replace the default Adapter
Now that we have created our custom adapter, we need to replace the default adapter generated by Android Studio. Before we do that, remember that the generated adapter has an inline array of Strings, well we need to replace that too with another list of something that contains our fragments. At the top of the NavigationDrawer element go ahead and add these to lines of code:
private List dataList;
private CustomDrawerAdapter mDrawerAdapter;
Now go down, under the onCreateView() method and instantiate the list that you defined above with the code below, remember our DrawerItem class, now its coming to good use.
dataList = new ArrayList();
Next, replace the code that created the inline adapter with an instance of our custom adapter and supply our custom row layout like below:
mDrawerAdapter = new CustomDrawerAdapter(getActivity(), R.layout.custom_drawer_item, dataList);
mDrawerListView.setAdapter(mDrawerAdapter);
Now if you run the project, nothing will actually show in the list because we have not supplied our list with any item. So under where you instantiated the datalist, add a method to populate the list. I have called my method addItemsToDataList so it looks like this:
dataList = new ArrayList();
addItemsToDataList();
Now go anywhere in the NavigationDrawerFragment class to define the method like below, I put mine at the bottom of the class.
private void addItemsToDataList() {
dataList.add(new DrawerItem("Customer", R.drawable.ic_action_social_group));
dataList.add(new DrawerItem("Products", R.drawable.ic_action_image_photo_camera));
dataList.add(new DrawerItem("Orders", R.drawable.ic_action_action_shopping_cart));
}
Here is where you add your top level fragments. The ones you want to show in the Navigation Drawer listview. You do not have to add your low level fragments here like the details fragments we created earlier. If you run the app now, you will see the image below. Go ahead and make sure you can run the app and it shows the screen below as we proceed to add Actionbar behavior next.
Step 6 – Update Title
The default behaviour of Navigation Drawer is to show the title of the currently selected Fragment in the Navigation drawer. We will update the default template to also account for our lower lower fragment. The title is updated in the MainActivity, it has a method called onSectionAttached and accepts an int like so:
public void onSectionAttached(int number) {
switch (number) {
case 1:
mTitle = getString(R.string.title_section1);
break;
case 2:
mTitle = getString(R.string.title_section2);
break;
case 3:
mTitle = getString(R.string.title_section3);
break;
}
}
Update the code to show Customer, Products, Order, etc to reflect the names you want to show for each of your Fragment. You can also add add your order lower lever fragment.Now, how does it know which one to show, it gets it from the supplied int parameter and this parameter and this parameter is supplied via the NewInstance method of each fragment. Here is my updated onSectionAttachedMethod:
public void onSectionAttached(int number) {
switch (number) {
case 0:
mTitle = getString(R.string.title_section0);
break;
case 1:
mTitle = getString(R.string.title_section1);
break;
case 2:
mTitle = getString(R.string.title_section2);
break;
case 3:
mTitle = getString(R.string.title_section3);
break;
case 4:
mTitle = getString(R.string.title_section4);
break;
case 5:
mTitle = getString(R.string.title_section5);
break;
}
}
Step 7 – Create New Fragment
Currently the auto generated code uses PlaceHolderFragment and for each time something is selected in the Navigation list like this:
@Override
public void onNavigationDrawerItemSelected(int position) {
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
.commit();
}
We will update this to actually start creating something when its clicked. Delete the PlaceHolder Fragment. We need to create the fragments using their respective newInstance method. NewInstance is a thoroughly debated and generally accepted design pattern that you will do well to follow.
@Override
public void onNavigationDrawerItemSelected(int position) {
Fragment mFragment = null;
switch (position){
case 0:
mFragment = CustomerListFragment.newInstance(0);
break;
case 1:
mFragment = ProductListFragment.newInstance(1);
break;
case 2:
mFragment = OrderListFragment.newInstance(2);
break;
}
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, mFragment)
.addToBackStack(null)
.commit();
}
For each of your fragment add or update the newInstance method like this:
public static CustomerListFragment newInstance(int sectionNumber) {
CustomerListFragment fragment = new CustomerListFragment();
Bundle args = new Bundle();
args.putInt(Constants.ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
I defined a constant to hold the reference like so:
public static final String ARG_SECTION_NUMBER = "section_number";
Then in the onAttach() method of each of your fragment, call the onSectionAttached method of your MainActivity, to update the title with whatever title you supplied to that number. Here is the onAttach callback method:
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((MainActivity) activity).onSectionAttached(
getArguments().getInt(Constants.ARG_SECTION_NUMBER));
}
Make sure that your Fragments are using the Support library fragment. You will need to define the NewInstance methods and the onAttach call back methods for your lower level Activities.
Step 7 – Add Lower Level Fragments
You can go to your lower level Fragment any way that makes sense for your app. It could be through an onClickListner event in your listview. It could be through a menu item. Either way, you should probably be aware that it is not recommended that you start a Fragment from a Fragment, so you will need to call the Activity and inform it to start a Fragment for you. One way you can accomplish this it to create a method in the MainActivity that does this.
Then you can make that method accept an int and then create a switch statement using that int. Or better still, you can create an Enum so you will not have to be remembering numbers. Below is an example of how you can start a new fragment from a menu and example of a method in the ReplaceFragment method in the Main Activity.
First add a menu item xml like this
<menu>
<menu>
Then in the CustomerListFragment, add and handle the menu icon to create a lower level Fragment like this:
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.customer_list_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id){
case R.id.action_add_customer:
MainActivity myActivity = (MainActivity)getActivity();
myActivity.ReplaceFragment(Enums.FragmentEnums.CustomerDetailsFragment, 4);
}
return super.onOptionsItemSelected(item);
}
Step 8 – Switch the Navigation Drawer Icon with the Up Icon for Lower Level Fragments
Notice that in the above call in the Fragment, we are calling a method in the Main Activity. What this method does,is first it hides the Navigation Drawer icon and then start the called fragment. To actually make the Up button to show in the Action bar, you will have to first call SetHasOptionMenu in each of the lower level Fragment to show the menu items and then call set display Action bar up button like so:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
GetCurrentService();
ActionBar actionBar = ((ActionBarActivity) getActivity()).getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
Then here is the method that does the switch of the Fragment in the MainActivity
public void ReplaceFragment(Enums.FragmentEnums frag, int sectionNumber){
NavigationDrawerFragment navFrag = (NavigationDrawerFragment)
getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
FragmentManager fragmentManager = getSupportFragmentManager();
switch (frag){
case CustomerDetailsFragment:
CustomerDetailsFragment serviceFrag = CustomerDetailsFragment.newInstance(sectionNumber);
navFrag.mDrawerToggle.setDrawerIndicatorEnabled(false);
fragmentManager.beginTransaction().replace(R.id.container, serviceFrag)
.addToBackStack(null).commit();
break;
case ProductDetailsFragment:
ProductDetailsFragment profileFrag = ProductDetailsFragment.newInstance(sectionNumber);
navFrag.mDrawerToggle.setDrawerIndicatorEnabled(false);
fragmentManager.beginTransaction().replace(R.id.container,profileFrag)
.addToBackStack(null).commit();
break;
case OrderDetailsFragment:
OrderDetailsFragment clientFrag = OrderDetailsFragment.newInstance(sectionNumber);
navFrag.mDrawerToggle.setDrawerIndicatorEnabled(false);
fragmentManager.beginTransaction().replace(R.id.container,clientFrag)
.addToBackStack(null).commit();
break;
}
}
Step 9 – Go Back to Parent Fragment
To go back to a parent Fragment from a lower level Fragment, you override the OnBackPressed of the Main Activity like this:
@Override
public void onBackPressed() {
super.onBackPressed();
NavigationDrawerFragment navFrag = (NavigationDrawerFragment)
getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
navFrag.mDrawerToggle.setDrawerIndicatorEnabled(true);
onSectionAttached(navFrag.mCurrentSelectedPosition);
}
Then each time you want to back to a parent Fragment you call that method from a lower level Fragment like this:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
getActivity().onBackPressed();
break;
}
return super.onOptionsItemSelected(item);
}
Points to Note
- All your Fragment will be from the Support Library otherwise you will get error when you call getSupportFragmentManager
- This does not work with Toolbar, atleast not without significant hack as shown in this StackOverFlow question
- You do not need a custom adapter if you do not need an image in the Navigation Drawer List
- Remove the action_example menu item from main.xml in the menu.
- The mDrawerToggle and mCurrentSelectionPosition in the NavigationFragment should be made public so it can be accessible from the MainActivity
Summary
That is it, this should get you a working custom Navigation Drawer that you further flush out to fit your project. You can find the source code for the demo project from my Github repo and if you do something interesting with it please submit a pull request so others can benefit from it. And if you find something not working please use the comment to let me know. Thanks for reading.In
The post Custom Android Navigation Drawer appeared first on Val Okafor.