blob: 38b7345456dd1e148688e7e93af79bba78fa3e7b [file] [log] [blame]
page.title=Creating a Navigation Drawer
page.tags="DrawerLayout", "navigation"
trainingnavtop=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you to:</h2>
<ol>
<li><a href="#DrawerLayout">Create a Drawer Layout</a></li>
<li><a href="#Init">Initialize the Drawer List</a></li>
<li><a href="#ListItemClicks">Handle Navigation Click Events</a></li>
<li><a href="#OpenClose">Listen for Open and Close Events</a></li>
<li><a href="#ActionBarIcon">Open and Close with the App Icon</a></li>
</ol>
<h2>Try it out</h2>
<div class="download-box">
<a href="http://developer.android.com/shareables/training/NavigationDrawer.zip"
class="button">Download the sample app</a>
<p class="filename">NavigationDrawer.zip</p>
</div>
<div class="download-box">
<a href="http://developer.android.com/downloads/design/Android_Navigation_Drawer_Icon_20130516.zip"
class="button">Download the nav drawer icons</a>
<p class="filename">Android_Navigation_Drawer_Icon_20130516.zip</p>
</div>
</div>
</div>
<p>The navigation drawer is a panel that displays the app’s main navigation options
on the left edge of the screen. It is hidden most of the time, but is revealed
when the user swipes a finger from the left edge of the screen or, while at the top level of the
app, the user touches the app icon in the action bar.</p>
<p>This lesson describes how to implement a navigation drawer using the
{@link android.support.v4.widget.DrawerLayout} APIs available in the
<a href="{@docRoot}tools/extras/support-library.html">Support Library</a>.</p>
<div class="note design">
<p><strong>Navigation Drawer Design</strong></p>
<p>Before you decide to use a navigation drawer in your app, you should understand the use
cases and design principles defined in the
<a href="{@docRoot}design/patterns/navigation-drawer.html">Navigation Drawer</a> design guide.</p>
</div>
<h2 id="DrawerLayout">Create a Drawer Layout</h2>
<p>To add a navigation drawer, declare your user interface with a
{@link android.support.v4.widget.DrawerLayout} object as the root view of your layout.
Inside the {@link android.support.v4.widget.DrawerLayout}, add one view that contains
the main content for the screen (your primary layout when the drawer is hidden) and another view
that contains the contents of the navigation drawer.</p>
<p>For example, the following layout uses a {@link
android.support.v4.widget.DrawerLayout} with two child views: a {@link android.widget.FrameLayout}
to contain the main content (populated by a {@link android.app.Fragment} at
runtime), and a {@link android.widget.ListView} for the navigation drawer.</p>
<pre>
&lt;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">
&lt;!-- The main content view -->
&lt;FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
&lt;!-- The navigation drawer -->
&lt;ListView android:id="@+id/left_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="&#64;android:color/transparent"
android:dividerHeight="0dp"
android:background="#111"/>
&lt;/android.support.v4.widget.DrawerLayout>
</pre>
<p>This layout demonstrates some important layout characteristics:</p>
<ul>
<li>The main content view (the {@link android.widget.FrameLayout} above)
<strong>must be the first child</strong> in the {@link
android.support.v4.widget.DrawerLayout} because the XML order implies z-ordering
and the drawer must be on top of the content.</li>
<li>The main content view is set to match the parent
view's width and height, because it represents the entire UI when the
navigation drawer is hidden.</li>
<li>The drawer view (the {@link android.widget.ListView}) <strong>must specify its horizontal
gravity</strong> with the {@code android:layout_gravity} attribute. To
support right-to-left (RTL) languages, specify the value with {@code "start"}
instead of {@code "left"} (so the drawer appears on the right when the layout is RTL).</p>
</li>
<li>The drawer view specifies its width in {@code dp} units and the height matches the parent
view. The drawer width should be no more than 320dp so the user can always
see a portion of the main content.</li>
</ul>
<h2 id="Init">Initialize the Drawer List</h2>
<p>In your activity, one of the first things to do is initialize
the navigation drawer's list of items. How you do so depends on the content of your app, but
a navigation drawer often consists of a {@link android.widget.ListView}, so the list
should be populated by an {@link android.widget.Adapter} (such as {@link
android.widget.ArrayAdapter} or {@link android.widget.SimpleCursorAdapter}).</p>
<p>For example, here's how you can initialize the navigation list with a
<a href="{@docRoot}guide/topics/resources/string-resource.html#StringArray">string array</a>:</p>
<pre>
public class MainActivity extends Activity {
private String[] mPlanetTitles;
private DrawerLayout mDrawerLayout;
private ListView mDrawerList;
...
&#64;Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPlanetTitles = getResources().getStringArray(R.array.planets_array);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.left_drawer);
// Set the adapter for the list view
mDrawerList.setAdapter(new ArrayAdapter&lt;String>(this,
R.layout.drawer_list_item, mPlanetTitles));
// Set the list's click listener
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
...
}
}
</pre>
<p>This code also calls {@link android.widget.ListView#setOnItemClickListener
setOnItemClickListener()} to receive click events in the navigation drawer's list.
The next section shows how to implement this interface
and change the content view when the user selects an item.</p>
<h2 id="ListItemClicks">Handle Navigation Click Events</h2>
<p>When the user selects an item in the drawer's list, the system calls {@link
android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()} on the
{@link android.widget.AdapterView.OnItemClickListener OnItemClickListener} given to
{@link android.widget.ListView#setOnItemClickListener setOnItemClickListener()}.</p>
<p>What you do in the {@link
android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()} method
depends on how you've implemented your <a
href="{@docRoot}design/patterns/app-structure.html">app structure</a>. In the following example,
selecting each item in the list inserts a different {@link
android.app.Fragment} into the main content view (the
{@link android.widget.FrameLayout} element identified by the {@code R.id.content_frame} ID):</p>
<pre>
private class DrawerItemClickListener implements ListView.OnItemClickListener {
&#64;Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
/** Swaps fragments in the main content view */
private void selectItem(int position) {
// Create a new fragment and specify the planet to show based on position
Fragment fragment = new PlanetFragment();
Bundle args = new Bundle();
args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
fragment.setArguments(args);
// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragment)
.commit();
// Highlight the selected item, update the title, and close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(mPlanetTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
&#64;Override
public void setTitle(CharSequence title) {
mTitle = title;
getActionBar().setTitle(mTitle);
}
</pre>
<h2 id="OpenClose">Listen for Open and Close Events</h2>
<p>To listen for drawer open and close events, call {@link
android.support.v4.widget.DrawerLayout#setDrawerListener setDrawerListener()} on your
{@link android.support.v4.widget.DrawerLayout} and pass it an implementation of
{@link android.support.v4.widget.DrawerLayout.DrawerListener}. This interface provides callbacks
for drawer events such as {@link
android.support.v4.widget.DrawerLayout.DrawerListener#onDrawerOpened onDrawerOpened()} and {@link
android.support.v4.widget.DrawerLayout.DrawerListener#onDrawerClosed onDrawerClosed()}.</p>
<p>However, rather than implementing the {@link
android.support.v4.widget.DrawerLayout.DrawerListener}, if your activity includes the
<a href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>, you can instead
extend the {@link android.support.v4.app.ActionBarDrawerToggle} class. The
{@link android.support.v4.app.ActionBarDrawerToggle} implements
{@link android.support.v4.widget.DrawerLayout.DrawerListener} so you can still override those
callbacks, but it also facilitates the proper
interaction behavior between the action bar icon and the navigation drawer (discussed further in
the next section).</p>
<p>As discussed in the <a href="{@docRoot}design/patterns/navigation-drawer.html">Navigation
Drawer</a> design guide, you should modify the contents of the action bar
when the drawer is visible, such as to change the title and remove action items that are
contextual to the main content. The following code shows how you can do so by overriding {@link
android.support.v4.widget.DrawerLayout.DrawerListener} callback methods with an instance
of the {@link android.support.v4.app.ActionBarDrawerToggle} class:</p>
<pre>
public class MainActivity extends Activity {
private DrawerLayout mDrawerLayout;
private ActionBarDrawerToggle mDrawerToggle;
private CharSequence mDrawerTitle;
private CharSequence mTitle;
...
&#64;Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
mTitle = mDrawerTitle = getTitle();
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {
/** Called when a drawer has settled in a completely closed state. */
public void onDrawerClosed(View view) {
getActionBar().setTitle(mTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View drawerView) {
getActionBar().setTitle(mDrawerTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};
// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
}
/* Called whenever we call invalidateOptionsMenu() */
&#64;Override
public boolean onPrepareOptionsMenu(Menu menu) {
// If the nav drawer is open, hide action items related to the content view
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
}
</pre>
<p>The next section describes the {@link android.support.v4.app.ActionBarDrawerToggle} constructor
arguments and the other steps required to set it up to handle interaction with the
action bar icon.</p>
<h2 id="ActionBarIcon">Open and Close with the App Icon</h2>
<p>Users can open and close the navigation drawer with a swipe gesture from or towards the left
edge of the screen, but if you're using the <a
href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>, you should also allow users to
open and close it by touching the app icon. And the app icon should also indicate the presence of
the navigation drawer with a special icon. You can implement all this behavior by using the
{@link android.support.v4.app.ActionBarDrawerToggle} shown in the previous section.</p>
<p>To make {@link android.support.v4.app.ActionBarDrawerToggle} work, create an instance of
it with its constructor, which requires the following arguments:</p>
<ul>
<li>The {@link android.app.Activity} hosting the drawer.
<li>The {@link android.support.v4.widget.DrawerLayout}.
<li>A drawable resource to use as the drawer indicator.
<p><a href="http://developer.android.com/downloads/design/Android_Navigation_Drawer_Icon_20130516.zip"
>Download the standard navigation icons</a> (available for both dark and light themes).</p>
<li>A String resource to describe the "open drawer" action (for accessibility).
<li>A String resource to describe the "close drawer" action (for accessibility).
</ul>
<p>Then, whether or not you've created a subclass of
{@link android.support.v4.app.ActionBarDrawerToggle} as your drawer listener, you need to call
upon your {@link android.support.v4.app.ActionBarDrawerToggle} in a few places throughout your
activity lifecycle:</p>
<pre>
public class MainActivity extends Activity {
private DrawerLayout mDrawerLayout;
private ActionBarDrawerToggle mDrawerToggle;
...
public void onCreate(Bundle savedInstanceState) {
...
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = new ActionBarDrawerToggle(
this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.drawable.ic_drawer, /* nav drawer icon to replace 'Up' caret */
R.string.drawer_open, /* "open drawer" description */
R.string.drawer_close /* "close drawer" description */
) {
/** Called when a drawer has settled in a completely closed state. */
public void onDrawerClosed(View view) {
getActionBar().setTitle(mTitle);
}
/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View drawerView) {
getActionBar().setTitle(mDrawerTitle);
}
};
// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
}
&#64;Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
&#64;Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig);
}
&#64;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);
}
...
}
</pre>
<p>For a complete example of a navigation drawer, download the sample available at the
<a href="#top">top of the page</a>.</p>