In this tutorial, we are going to learn how to combine android navigation drawer and master detail flow in android. This tutorial is as a request I got from one of blog readers so I decided to write a tutorial about it. You can as well send a topic you will like me to cover. Finally, this is to inform you that this tutorial will be a bit long.
We are going to use Android Studio templates for Navigation Drawer and Master Detail flow. Then we have to integrate the two together seamlessly. We are going to create a new Fragment class which we will use to replace the default ItemListActivity class create by Android Studio.
Next, we will extract the default inner class adapter class – SimpleItemRecyclerViewAdapter to a separate file called SimpleItemRecyclerViewAdapter.java.
We will create a DefaultFragment class which we will use as a default in our Navigation Drawer. In onNavigationItemSelected method, we will create each Fragment that we will display in each menu.
Right now it is important for us to understand what we are planning to achieve in this tutorial. The screen-shot of our project is shown below.
Before we start, it is important for you to understand the tools and environment I used in this android tutorial. Feel free to use any tools you are familiar with.
Windows 7
Android Studio
Sony Xperia ZL
Min SDK 14
Target SDK 23
To create a new android application project, following the steps as stipulated below.
Go to File menu
Click on New menu
Click on Android Application
Enter Project name: AndroidNavigationDrawerMasterDetail
Package: com.inducesmile.androidnavigationdrawermasterdetail
Select Navigation Drawer Activity
Keep other default selections
Continue to click on next button until Finish button is active, then click on Finish Button.
If you are using Android Studio as your choice IDE, the new project will create a default
MainActivity.java file and four corresponding layout files.
The default layout files are the activity_main.xml, app_bar_main.xml, nav_header_main.xml and we will create a new layout file called example.xml. We will create two files for normal layout and for large screen layout.
The code snippet for activity_main.xml is as shown below.
<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start"> <include layout="@layout/app_bar_main" android:layout_width="match_parent" android:layout_height="match_parent" /> <android.support.design.widget.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/nav_header_main" app:menu="@menu/activity_main_drawer" /> </android.support.v4.widget.DrawerLayout>
The code snippet for app_bar_main.xml is as shown below
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="com.inducesmile.navigationdrawerandmasterdetail.MainActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/example" /> </android.support.design.widget.CoordinatorLayout>
The code snippet for nav_header_main.xml is as shown below
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/nav_header_height" android:background="@drawable/side_nav_bar" android:gravity="bottom" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:theme="@style/ThemeOverlay.AppCompat.Dark"> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="@dimen/nav_header_vertical_spacing" android:src="@android:drawable/sym_def_app_icon" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/nav_header_vertical_spacing" android:text="Android Studio" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="android.studio@android.com" /> </LinearLayout>
The code snippet for example.xml (normal display) is as shown below
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/first_container" android:layout_width="match_parent" android:layout_height="match_parent"> </FrameLayout>
The code snippet for example.xml (large display) is as shown below
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:baselineAligned="false" android:divider="?android:attr/dividerHorizontal" android:orientation="horizontal" android:showDividers="middle"> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/first_container" android:layout_width="@dimen/item_width" android:layout_height="match_parent"> </FrameLayout> <FrameLayout android:id="@+id/item_detail_container" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="3" /> </LinearLayout>
The default drawer menu is created in the menu folder. Open activity_main_drawer.xml. Feel free to modify or change whatever you want. The default code snippet is as shown below.
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_camera" android:icon="@drawable/ic_menu_camera" android:title="Import" /> <item android:id="@+id/nav_gallery" android:icon="@drawable/ic_menu_gallery" android:title="Gallery" /> <item android:id="@+id/nav_slideshow" android:icon="@drawable/ic_menu_slideshow" android:title="Slideshow" /> <item android:id="@+id/nav_manage" android:icon="@drawable/ic_menu_manage" android:title="Tools" /> </group> <item android:title="Communicate"> <menu> <item android:id="@+id/nav_share" android:icon="@drawable/ic_menu_share" android:title="Share" /> <item android:id="@+id/nav_send" android:icon="@drawable/ic_menu_send" android:title="Send" /> </menu> </item> </menu>
Lets create a new Master Detail Activity class. Click on the File menu, on the fly-out menu, click the new menu. On the pop-up menu, move down to the Activity menu and on the fly-out menu, click the Master/Detail Flow. After few seconds, the project we create a master/detail activity files and layout files.
The following Activities and Fragment Files are created for the Master/Detail Flow activity.
1. ItemListActivity.java – The Master ListView Activity class
2. ItemDetailActivity.java – The Detail Activity class
3. ItemDetailFragment.java – Detail Fragment class
Before we will go ahead with the content of these files, we will extract the SimpleItemRecyclerViewAdapter inner class and make it a separate file called SimpleItemRecyclerViewAdapter.java. The modify adapter class code is shown below.
package com.inducesmile.navigationdrawerandmasterdetail; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.inducesmile.navigationdrawerandmasterdetail.dummy.DummyContent; import java.util.List; public class SimpleItemRecyclerViewAdapter extends RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder> { private final List<DummyContent.DummyItem> mValues; private Context context; public SimpleItemRecyclerViewAdapter(Context context, List<DummyContent.DummyItem> items) { this.context = context; mValues = items; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list_content, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(final ViewHolder holder, int position) { holder.mItem = mValues.get(position); holder.mIdView.setText(mValues.get(position).id); holder.mContentView.setText(mValues.get(position).content); holder.mView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(!MainActivity.mTwoPane){ Context context = v.getContext(); Intent intent = new Intent(context, ItemDetailActivity.class); intent.putExtra(ItemDetailFragment.ARG_ITEM_ID, holder.mItem.id); context.startActivity(intent); }else { Bundle arguments = new Bundle(); arguments.putString(ItemDetailFragment.ARG_ITEM_ID, holder.mItem.id); ItemDetailFragment fragment = new ItemDetailFragment(); fragment.setArguments(arguments); ((MainActivity)context).getSupportFragmentManager().beginTransaction().replace(R.id.item_detail_container, fragment).commit(); } } }); } @Override public int getItemCount() { return mValues.size(); } public class ViewHolder extends RecyclerView.ViewHolder { public final View mView; public final TextView mIdView; public final TextView mContentView; public DummyContent.DummyItem mItem; public ViewHolder(View view) { super(view); mView = view; mIdView = (TextView) view.findViewById(R.id.id); mContentView = (TextView) view.findViewById(R.id.content); } @Override public String toString() { return super.toString() + " '" + mContentView.getText() + "'"; } } }
For the Detail Activity and Fragment files, we will not make any changes to them. Their code snippet is show below.
ItemDetailActivity.java
package com.inducesmile.navigationdrawerandmasterdetail; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.NavUtils; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MenuItem; /** * An activity representing a single Item detail screen. This * activity is only used narrow width devices. On tablet-size devices, * item details are presented side-by-side with a list of items * in a {@link ItemListActivity}. */ public class ItemDetailActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_item_detail); Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar); setSupportActionBar(toolbar); // Show the Up button in the action bar. ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); } // savedInstanceState is non-null when there is fragment state // saved from previous configurations of this activity // (e.g. when rotating the screen from portrait to landscape). // In this case, the fragment will automatically be re-added // to its container so we don't need to manually add it. // For more information, see the Fragments API guide at: // // http://developer.android.com/guide/components/fragments.html // if (savedInstanceState == null) { // Create the detail fragment and add it to the activity // using a fragment transaction. Bundle arguments = new Bundle(); arguments.putString(ItemDetailFragment.ARG_ITEM_ID, getIntent().getStringExtra(ItemDetailFragment.ARG_ITEM_ID)); ItemDetailFragment fragment = new ItemDetailFragment(); fragment.setArguments(arguments); getSupportFragmentManager().beginTransaction() .add(R.id.item_detail_container, fragment) .commit(); } } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { // This ID represents the Home or Up button. In the case of this // activity, the Up button is shown. Use NavUtils to allow users // to navigate up one level in the application structure. For // more details, see the Navigation pattern on Android Design: // // http://developer.android.com/design/patterns/navigation.html#up-vs-back // NavUtils.navigateUpTo(this, new Intent(this, ItemListActivity.class)); return true; } return super.onOptionsItemSelected(item); } }
ItemDetailFragment.java
package com.inducesmile.navigationdrawerandmasterdetail; import android.app.Activity; import android.support.design.widget.CollapsingToolbarLayout; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.inducesmile.navigationdrawerandmasterdetail.dummy.DummyContent; /** * A fragment representing a single Item detail screen. * This fragment is either contained in a {@link ItemListActivity} * in two-pane mode (on tablets) or a {@link ItemDetailActivity} * on handsets. */ public class ItemDetailFragment extends Fragment { /** * The fragment argument representing the item ID that this fragment * represents. */ public static final String ARG_ITEM_ID = "item_id"; /** * The dummy content this fragment is presenting. */ private DummyContent.DummyItem mItem; /** * Mandatory empty constructor for the fragment manager to instantiate the * fragment (e.g. upon screen orientation changes). */ public ItemDetailFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments().containsKey(ARG_ITEM_ID)) { // Load the dummy content specified by the fragment // arguments. In a real-world scenario, use a Loader // to load content from a content provider. mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID)); Activity activity = this.getActivity(); CollapsingToolbarLayout appBarLayout = (CollapsingToolbarLayout) activity.findViewById(R.id.toolbar_layout); if (appBarLayout != null) { appBarLayout.setTitle(mItem.content); } } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.item_detail, container, false); // Show the dummy content as text in a TextView. if (mItem != null) { ((TextView) rootView.findViewById(R.id.item_detail)).setText(mItem.details); } return rootView; } }
We are going to replace the default ItemListActivity.java file with a new java file called ItemListFragment.java.
Create a new Fragment file by clicking the File menu, the click the new menu and on the fly-out menu, select Fragment and clcik on the Blank Fragment menu to create it. The created Fragment file comes with a layout file called fragment_item_list.xml. The file contain a RecyclerView. The RecycleView instance will use the adapter we created before. The adapter will inflate the item_list.xml layout file to display each item of the RecyclerView. The code snippets is as shown below.
fragment_item_list.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/item_list" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="60dp" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" app:layoutManager="LinearLayoutManager" tools:listitem="@layout/item_list_content" />
The layout file code snippet for the adapter is shown below.
item_list_content.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/text_margin" android:textAppearance="?attr/textAppearanceListItem" /> <TextView android:id="@+id/content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/text_margin" android:textAppearance="?attr/textAppearanceListItem" /> </LinearLayout>
Lets move over to the ItemListFragment.java file. The instances of the RecyclerView and SimpleItemRecyclerViewAdapter are obtained. The adapter object is passed as a parameter in the setAdapter() method of the RecyclerView class. The code for this class is shown below.
ItemListFragment.java
package com.inducesmile.navigationdrawerandmasterdetail; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.inducesmile.navigationdrawerandmasterdetail.dummy.DummyContent; /** * A simple {@link Fragment} subclass. */ public class ItemListFragment extends Fragment { private RecyclerView mRecycleView; private SimpleItemRecyclerViewAdapter mSimpleItemRecyclerViewAdapter; public ItemListFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_item_list, container, false); mRecycleView = (RecyclerView)view.findViewById(R.id.item_list); mSimpleItemRecyclerViewAdapter = new SimpleItemRecyclerViewAdapter(getActivity(), DummyContent.ITEMS); mRecycleView.setAdapter(mSimpleItemRecyclerViewAdapter); return view; } }
To attach our ItemListFragment to our Navigation Drawer activity class, open the MainActivty.java file. First we will add the following lines of code. This code creates a default Fagment page which will be attached to the MainActivity class when the Activity is created or restarted.
FragmentManager fragmentManager = getSupportFragmentManager(); DefaultFragment fragment = new DefaultFragment(); fragmentManager.beginTransaction().replace(R.id.first_container, fragment).commit();
To make sure that the default Navigation Drawer menu is also selected at creation or initial load of the MainActivity class, we will call this one line of code.
navigationView.setCheckedItem(R.id.nav_camera);
Finally, we will make changes in onNavigationItemSelected(MenuItem item) whenever a new Navigation Drawer menu is selected. The complete code for this class is shown below.
MainActivity.java
package com.inducesmile.navigationdrawerandmasterdetail; import android.os.Bundle; import android.support.design.widget.NavigationView; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { public static boolean mTwoPane; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.setDrawerListener(toggle); toggle.syncState(); NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setCheckedItem(R.id.nav_camera); navigationView.setNavigationItemSelectedListener(this); if (findViewById(R.id.item_detail_container) != null) { // The detail container view will be present only in the // large-screen layouts (res/values-w900dp). // If this view is present, then the // activity should be in two-pane mode. mTwoPane = true; } FragmentManager fragmentManager = getSupportFragmentManager(); DefaultFragment fragment = new DefaultFragment(); fragmentManager.beginTransaction().replace(R.id.first_container, fragment).commit(); } @Override public void onBackPressed() { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } @SuppressWarnings("StatementWithEmptyBody") @Override public boolean onNavigationItemSelected(MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId(); Fragment fragment = null; FragmentManager fragmentManager = getSupportFragmentManager(); if (id == R.id.nav_camera) { // Handle the camera action fragment = new DefaultFragment(); fragmentManager.beginTransaction().replace(R.id.first_container, fragment).commit(); } else if (id == R.id.nav_gallery) { fragment = new ItemListFragment(); fragmentManager.beginTransaction().replace(R.id.first_container, fragment).commit(); } else if (id == R.id.nav_slideshow) { } else if (id == R.id.nav_manage) { } else if (id == R.id.nav_share) { } else if (id == R.id.nav_send) { } DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } }
Now, when you run your application you will see the interface that looks similar to the sample that was shown earlier on.
You can download the code for this tutorial below. If you are having hard time downloading the tutorials, kindly contact me.
Remember to subscribe with your email address so that you will be among the first to receive our new post once it is published.
If you like this tutorial, please kindly download my new android app – Daily Pocket Calculator – in Google Play Store and let me know what needs to be improved.
Hi.
Could you please post the code for the class DefaultFragment??.
I’m stucked in that point since you didn’t provide al code.
Great tutorial.
Thanks.
This is the content of the app
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A simple {@link Fragment} subclass.
*/
public class DefaultFragment extends Fragment {
public DefaultFragment() {
// 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_default, container, false);
}
}
Great.
I’ll give a try!!!
Thanks
Hi.
I have been you using your code and works great, I can add new fragments for my menu, but I still have one problem.
You show a initial Fragment called DefaultFragment that is linked to example.xml or example.xml (large).
If I use another xml file like example.xml for my fragment (modifying it), the content is showed over the toolbar, and I can’t see the way to fix this.
Anyway.
Thanks again.
Hi.
I tried to follow your tutorial but when I tried to create the new Master Detail Activity class Android Studio displayed a Configure Activity dialog and I get stucked.
Is it because of the Android Studio version?
Which Android Studio version are you using?
Android Studio for Mac v. 2.1.2
hi can’t download this project,can you plz give me the link?
I have added the download link. Good luck
I’m getting this error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{MainActivity}: java.lang.RuntimeException: MainActivity@42544f48 must implement OnFragmentInteractionListener
You should use a Blank Fragment Class which does not implement any interface class.
Hope it helps.