Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 1 | page.title=Creating a Navigation Drawer |
Joe Fernandez | 33baa5a | 2013-11-14 11:41:19 -0800 | [diff] [blame] | 2 | page.tags=DrawerLayout,navigation |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 3 | |
| 4 | trainingnavtop=true |
| 5 | |
| 6 | @jd:body |
| 7 | |
| 8 | <div id="tb-wrapper"> |
| 9 | <div id="tb"> |
| 10 | |
| 11 | <h2>This lesson teaches you to:</h2> |
| 12 | <ol> |
| 13 | <li><a href="#DrawerLayout">Create a Drawer Layout</a></li> |
| 14 | <li><a href="#Init">Initialize the Drawer List</a></li> |
| 15 | <li><a href="#ListItemClicks">Handle Navigation Click Events</a></li> |
| 16 | <li><a href="#OpenClose">Listen for Open and Close Events</a></li> |
| 17 | <li><a href="#ActionBarIcon">Open and Close with the App Icon</a></li> |
| 18 | </ol> |
| 19 | |
| 20 | <h2>Try it out</h2> |
| 21 | |
| 22 | <div class="download-box"> |
| 23 | <a href="http://developer.android.com/shareables/training/NavigationDrawer.zip" |
| 24 | class="button">Download the sample app</a> |
| 25 | <p class="filename">NavigationDrawer.zip</p> |
| 26 | </div> |
| 27 | |
Scott Main | 6782351 | 2013-05-16 10:41:15 -0700 | [diff] [blame] | 28 | <div class="download-box"> |
Scott Main | 4c9ed63 | 2013-09-26 12:39:27 -0700 | [diff] [blame] | 29 | <a href="http://developer.android.com/downloads/design/Android_Design_Icons_20130926.zip" |
| 30 | class="button">Download the Action Bar Icon Pack</a> |
| 31 | <p class="filename">Android_Design_Icons_20130926.zip</p> |
Scott Main | 6782351 | 2013-05-16 10:41:15 -0700 | [diff] [blame] | 32 | </div> |
| 33 | |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 34 | </div> |
| 35 | </div> |
| 36 | |
| 37 | |
| 38 | |
| 39 | <p>The navigation drawer is a panel that displays the app’s main navigation options |
| 40 | on the left edge of the screen. It is hidden most of the time, but is revealed |
| 41 | when the user swipes a finger from the left edge of the screen or, while at the top level of the |
| 42 | app, the user touches the app icon in the action bar.</p> |
| 43 | |
| 44 | <p>This lesson describes how to implement a navigation drawer using the |
| 45 | {@link android.support.v4.widget.DrawerLayout} APIs available in the |
Scott Main | 4e2c9dc | 2013-07-23 19:35:17 -0700 | [diff] [blame] | 46 | <a href="{@docRoot}tools/support-library/index.html">Support Library</a>.</p> |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 47 | |
| 48 | <div class="note design"> |
| 49 | <p><strong>Navigation Drawer Design</strong></p> |
| 50 | <p>Before you decide to use a navigation drawer in your app, you should understand the use |
| 51 | cases and design principles defined in the |
Scott Main | 62cd3ec | 2013-05-13 16:27:10 -0700 | [diff] [blame] | 52 | <a href="{@docRoot}design/patterns/navigation-drawer.html">Navigation Drawer</a> design guide.</p> |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 53 | </div> |
| 54 | |
| 55 | |
| 56 | <h2 id="DrawerLayout">Create a Drawer Layout</h2> |
| 57 | |
| 58 | <p>To add a navigation drawer, declare your user interface with a |
| 59 | {@link android.support.v4.widget.DrawerLayout} object as the root view of your layout. |
| 60 | Inside the {@link android.support.v4.widget.DrawerLayout}, add one view that contains |
| 61 | the main content for the screen (your primary layout when the drawer is hidden) and another view |
| 62 | that contains the contents of the navigation drawer.</p> |
| 63 | |
| 64 | <p>For example, the following layout uses a {@link |
| 65 | android.support.v4.widget.DrawerLayout} with two child views: a {@link android.widget.FrameLayout} |
| 66 | to contain the main content (populated by a {@link android.app.Fragment} at |
| 67 | runtime), and a {@link android.widget.ListView} for the navigation drawer.</p> |
| 68 | |
| 69 | <pre> |
| 70 | <android.support.v4.widget.DrawerLayout |
| 71 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 72 | android:id="@+id/drawer_layout" |
| 73 | android:layout_width="match_parent" |
| 74 | android:layout_height="match_parent"> |
| 75 | <!-- The main content view --> |
| 76 | <FrameLayout |
| 77 | android:id="@+id/content_frame" |
| 78 | android:layout_width="match_parent" |
| 79 | android:layout_height="match_parent" /> |
| 80 | <!-- The navigation drawer --> |
| 81 | <ListView android:id="@+id/left_drawer" |
| 82 | android:layout_width="240dp" |
| 83 | android:layout_height="match_parent" |
| 84 | android:layout_gravity="start" |
| 85 | android:choiceMode="singleChoice" |
| 86 | android:divider="@android:color/transparent" |
| 87 | android:dividerHeight="0dp" |
| 88 | android:background="#111"/> |
| 89 | </android.support.v4.widget.DrawerLayout> |
| 90 | </pre> |
| 91 | |
| 92 | <p>This layout demonstrates some important layout characteristics:</p> |
| 93 | <ul> |
| 94 | <li>The main content view (the {@link android.widget.FrameLayout} above) |
| 95 | <strong>must be the first child</strong> in the {@link |
| 96 | android.support.v4.widget.DrawerLayout} because the XML order implies z-ordering |
| 97 | and the drawer must be on top of the content.</li> |
| 98 | <li>The main content view is set to match the parent |
| 99 | view's width and height, because it represents the entire UI when the |
| 100 | navigation drawer is hidden.</li> |
| 101 | <li>The drawer view (the {@link android.widget.ListView}) <strong>must specify its horizontal |
| 102 | gravity</strong> with the {@code android:layout_gravity} attribute. To |
| 103 | support right-to-left (RTL) languages, specify the value with {@code "start"} |
| 104 | instead of {@code "left"} (so the drawer appears on the right when the layout is RTL).</p> |
| 105 | </li> |
| 106 | <li>The drawer view specifies its width in {@code dp} units and the height matches the parent |
| 107 | view. The drawer width should be no more than 320dp so the user can always |
| 108 | see a portion of the main content.</li> |
| 109 | </ul> |
| 110 | |
| 111 | |
| 112 | |
| 113 | <h2 id="Init">Initialize the Drawer List</h2> |
| 114 | |
| 115 | <p>In your activity, one of the first things to do is initialize |
| 116 | the navigation drawer's list of items. How you do so depends on the content of your app, but |
| 117 | a navigation drawer often consists of a {@link android.widget.ListView}, so the list |
| 118 | should be populated by an {@link android.widget.Adapter} (such as {@link |
| 119 | android.widget.ArrayAdapter} or {@link android.widget.SimpleCursorAdapter}).</p> |
| 120 | |
| 121 | <p>For example, here's how you can initialize the navigation list with a |
| 122 | <a href="{@docRoot}guide/topics/resources/string-resource.html#StringArray">string array</a>:</p> |
| 123 | |
| 124 | <pre> |
| 125 | public class MainActivity extends Activity { |
| 126 | private String[] mPlanetTitles; |
Scott Main | d25cc95 | 2013-07-18 10:18:57 -0700 | [diff] [blame] | 127 | private DrawerLayout mDrawerLayout; |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 128 | private ListView mDrawerList; |
| 129 | ... |
| 130 | |
| 131 | @Override |
| 132 | public void onCreate(Bundle savedInstanceState) { |
| 133 | super.onCreate(savedInstanceState); |
| 134 | setContentView(R.layout.activity_main); |
| 135 | |
| 136 | mPlanetTitles = getResources().getStringArray(R.array.planets_array); |
Scott Main | d25cc95 | 2013-07-18 10:18:57 -0700 | [diff] [blame] | 137 | mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 138 | mDrawerList = (ListView) findViewById(R.id.left_drawer); |
| 139 | |
| 140 | // Set the adapter for the list view |
| 141 | mDrawerList.setAdapter(new ArrayAdapter<String>(this, |
| 142 | R.layout.drawer_list_item, mPlanetTitles)); |
| 143 | // Set the list's click listener |
| 144 | mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); |
| 145 | |
| 146 | ... |
| 147 | } |
| 148 | } |
| 149 | </pre> |
| 150 | |
| 151 | <p>This code also calls {@link android.widget.ListView#setOnItemClickListener |
| 152 | setOnItemClickListener()} to receive click events in the navigation drawer's list. |
| 153 | The next section shows how to implement this interface |
| 154 | and change the content view when the user selects an item.</p> |
| 155 | |
| 156 | |
| 157 | |
| 158 | <h2 id="ListItemClicks">Handle Navigation Click Events</h2> |
| 159 | |
| 160 | <p>When the user selects an item in the drawer's list, the system calls {@link |
| 161 | android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()} on the |
| 162 | {@link android.widget.AdapterView.OnItemClickListener OnItemClickListener} given to |
| 163 | {@link android.widget.ListView#setOnItemClickListener setOnItemClickListener()}.</p> |
| 164 | |
| 165 | <p>What you do in the {@link |
| 166 | android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()} method |
| 167 | depends on how you've implemented your <a |
| 168 | href="{@docRoot}design/patterns/app-structure.html">app structure</a>. In the following example, |
| 169 | selecting each item in the list inserts a different {@link |
| 170 | android.app.Fragment} into the main content view (the |
| 171 | {@link android.widget.FrameLayout} element identified by the {@code R.id.content_frame} ID):</p> |
| 172 | |
| 173 | <pre> |
| 174 | private class DrawerItemClickListener implements ListView.OnItemClickListener { |
| 175 | @Override |
| 176 | public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| 177 | selectItem(position); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | /** Swaps fragments in the main content view */ |
| 182 | private void selectItem(int position) { |
| 183 | // Create a new fragment and specify the planet to show based on position |
| 184 | Fragment fragment = new PlanetFragment(); |
| 185 | Bundle args = new Bundle(); |
| 186 | args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position); |
| 187 | fragment.setArguments(args); |
| 188 | |
| 189 | // Insert the fragment by replacing any existing fragment |
| 190 | FragmentManager fragmentManager = getFragmentManager(); |
| 191 | fragmentManager.beginTransaction() |
| 192 | .replace(R.id.content_frame, fragment) |
| 193 | .commit(); |
| 194 | |
| 195 | // Highlight the selected item, update the title, and close the drawer |
Scott Main | d25cc95 | 2013-07-18 10:18:57 -0700 | [diff] [blame] | 196 | mDrawerList.setItemChecked(position, true); |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 197 | setTitle(mPlanetTitles[position]); |
Scott Main | d25cc95 | 2013-07-18 10:18:57 -0700 | [diff] [blame] | 198 | mDrawerLayout.closeDrawer(mDrawerList); |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 199 | } |
| 200 | |
| 201 | @Override |
| 202 | public void setTitle(CharSequence title) { |
| 203 | mTitle = title; |
| 204 | getActionBar().setTitle(mTitle); |
| 205 | } |
| 206 | |
| 207 | </pre> |
| 208 | |
| 209 | |
| 210 | |
| 211 | |
| 212 | <h2 id="OpenClose">Listen for Open and Close Events</h2> |
| 213 | |
| 214 | <p>To listen for drawer open and close events, call {@link |
| 215 | android.support.v4.widget.DrawerLayout#setDrawerListener setDrawerListener()} on your |
| 216 | {@link android.support.v4.widget.DrawerLayout} and pass it an implementation of |
| 217 | {@link android.support.v4.widget.DrawerLayout.DrawerListener}. This interface provides callbacks |
| 218 | for drawer events such as {@link |
| 219 | android.support.v4.widget.DrawerLayout.DrawerListener#onDrawerOpened onDrawerOpened()} and {@link |
| 220 | android.support.v4.widget.DrawerLayout.DrawerListener#onDrawerClosed onDrawerClosed()}.</p> |
| 221 | |
| 222 | <p>However, rather than implementing the {@link |
| 223 | android.support.v4.widget.DrawerLayout.DrawerListener}, if your activity includes the |
| 224 | <a href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>, you can instead |
| 225 | extend the {@link android.support.v4.app.ActionBarDrawerToggle} class. The |
| 226 | {@link android.support.v4.app.ActionBarDrawerToggle} implements |
| 227 | {@link android.support.v4.widget.DrawerLayout.DrawerListener} so you can still override those |
| 228 | callbacks, but it also facilitates the proper |
| 229 | interaction behavior between the action bar icon and the navigation drawer (discussed further in |
| 230 | the next section).</p> |
| 231 | |
Scott Main | 62cd3ec | 2013-05-13 16:27:10 -0700 | [diff] [blame] | 232 | <p>As discussed in the <a href="{@docRoot}design/patterns/navigation-drawer.html">Navigation |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 233 | Drawer</a> design guide, you should modify the contents of the action bar |
| 234 | when the drawer is visible, such as to change the title and remove action items that are |
| 235 | contextual to the main content. The following code shows how you can do so by overriding {@link |
| 236 | android.support.v4.widget.DrawerLayout.DrawerListener} callback methods with an instance |
| 237 | of the {@link android.support.v4.app.ActionBarDrawerToggle} class:</p> |
| 238 | |
| 239 | <pre> |
| 240 | public class MainActivity extends Activity { |
| 241 | private DrawerLayout mDrawerLayout; |
| 242 | private ActionBarDrawerToggle mDrawerToggle; |
| 243 | private CharSequence mDrawerTitle; |
| 244 | private CharSequence mTitle; |
| 245 | ... |
| 246 | |
| 247 | @Override |
| 248 | public void onCreate(Bundle savedInstanceState) { |
| 249 | super.onCreate(savedInstanceState); |
| 250 | setContentView(R.layout.activity_main); |
| 251 | ... |
| 252 | |
| 253 | mTitle = mDrawerTitle = getTitle(); |
| 254 | mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); |
| 255 | mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, |
| 256 | R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) { |
| 257 | |
| 258 | /** Called when a drawer has settled in a completely closed state. */ |
| 259 | public void onDrawerClosed(View view) { |
Ricardo Cervera | b96c0ab | 2014-01-15 15:16:30 -0800 | [diff] [blame] | 260 | super.onDrawerClosed(view); |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 261 | getActionBar().setTitle(mTitle); |
| 262 | invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() |
| 263 | } |
| 264 | |
| 265 | /** Called when a drawer has settled in a completely open state. */ |
| 266 | public void onDrawerOpened(View drawerView) { |
Ricardo Cervera | b96c0ab | 2014-01-15 15:16:30 -0800 | [diff] [blame] | 267 | super.onDrawerOpened(drawerView); |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 268 | getActionBar().setTitle(mDrawerTitle); |
| 269 | invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() |
| 270 | } |
| 271 | }; |
| 272 | |
| 273 | // Set the drawer toggle as the DrawerListener |
| 274 | mDrawerLayout.setDrawerListener(mDrawerToggle); |
| 275 | } |
| 276 | |
| 277 | /* Called whenever we call invalidateOptionsMenu() */ |
| 278 | @Override |
| 279 | public boolean onPrepareOptionsMenu(Menu menu) { |
| 280 | // If the nav drawer is open, hide action items related to the content view |
| 281 | boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); |
| 282 | menu.findItem(R.id.action_websearch).setVisible(!drawerOpen); |
| 283 | return super.onPrepareOptionsMenu(menu); |
| 284 | } |
| 285 | } |
| 286 | </pre> |
| 287 | |
| 288 | <p>The next section describes the {@link android.support.v4.app.ActionBarDrawerToggle} constructor |
| 289 | arguments and the other steps required to set it up to handle interaction with the |
| 290 | action bar icon.</p> |
| 291 | |
| 292 | |
| 293 | |
| 294 | <h2 id="ActionBarIcon">Open and Close with the App Icon</h2> |
| 295 | |
| 296 | <p>Users can open and close the navigation drawer with a swipe gesture from or towards the left |
| 297 | edge of the screen, but if you're using the <a |
| 298 | href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>, you should also allow users to |
| 299 | open and close it by touching the app icon. And the app icon should also indicate the presence of |
| 300 | the navigation drawer with a special icon. You can implement all this behavior by using the |
| 301 | {@link android.support.v4.app.ActionBarDrawerToggle} shown in the previous section.</p> |
| 302 | |
| 303 | <p>To make {@link android.support.v4.app.ActionBarDrawerToggle} work, create an instance of |
| 304 | it with its constructor, which requires the following arguments:</p> |
| 305 | <ul> |
| 306 | <li>The {@link android.app.Activity} hosting the drawer. |
| 307 | <li>The {@link android.support.v4.widget.DrawerLayout}. |
| 308 | <li>A drawable resource to use as the drawer indicator. |
Scott Main | 4c9ed63 | 2013-09-26 12:39:27 -0700 | [diff] [blame] | 309 | <p>The standard navigation drawer icon is available in the <a href="http://developer.android.com/downloads/design/Android_Design_Icons_20130926.zip" |
| 310 | >Download the Action Bar Icon Pack</a>.</p> |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 311 | <li>A String resource to describe the "open drawer" action (for accessibility). |
| 312 | <li>A String resource to describe the "close drawer" action (for accessibility). |
| 313 | </ul> |
| 314 | |
| 315 | <p>Then, whether or not you've created a subclass of |
| 316 | {@link android.support.v4.app.ActionBarDrawerToggle} as your drawer listener, you need to call |
| 317 | upon your {@link android.support.v4.app.ActionBarDrawerToggle} in a few places throughout your |
| 318 | activity lifecycle:</p> |
| 319 | |
| 320 | <pre> |
| 321 | public class MainActivity extends Activity { |
| 322 | private DrawerLayout mDrawerLayout; |
| 323 | private ActionBarDrawerToggle mDrawerToggle; |
| 324 | ... |
| 325 | |
| 326 | public void onCreate(Bundle savedInstanceState) { |
| 327 | ... |
| 328 | |
| 329 | mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); |
Scott Main | 6782351 | 2013-05-16 10:41:15 -0700 | [diff] [blame] | 330 | mDrawerToggle = new ActionBarDrawerToggle( |
| 331 | this, /* host Activity */ |
| 332 | mDrawerLayout, /* DrawerLayout object */ |
| 333 | R.drawable.ic_drawer, /* nav drawer icon to replace 'Up' caret */ |
| 334 | R.string.drawer_open, /* "open drawer" description */ |
| 335 | R.string.drawer_close /* "close drawer" description */ |
| 336 | ) { |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 337 | |
| 338 | /** Called when a drawer has settled in a completely closed state. */ |
| 339 | public void onDrawerClosed(View view) { |
Ricardo Cervera | b96c0ab | 2014-01-15 15:16:30 -0800 | [diff] [blame] | 340 | super.onDrawerClosed(view); |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 341 | getActionBar().setTitle(mTitle); |
| 342 | } |
| 343 | |
| 344 | /** Called when a drawer has settled in a completely open state. */ |
| 345 | public void onDrawerOpened(View drawerView) { |
Ricardo Cervera | b96c0ab | 2014-01-15 15:16:30 -0800 | [diff] [blame] | 346 | super.onDrawerOpened(drawerView); |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 347 | getActionBar().setTitle(mDrawerTitle); |
| 348 | } |
| 349 | }; |
| 350 | |
| 351 | // Set the drawer toggle as the DrawerListener |
| 352 | mDrawerLayout.setDrawerListener(mDrawerToggle); |
| 353 | |
| 354 | getActionBar().setDisplayHomeAsUpEnabled(true); |
| 355 | getActionBar().setHomeButtonEnabled(true); |
| 356 | } |
| 357 | |
| 358 | @Override |
| 359 | protected void onPostCreate(Bundle savedInstanceState) { |
| 360 | super.onPostCreate(savedInstanceState); |
| 361 | // Sync the toggle state after onRestoreInstanceState has occurred. |
| 362 | mDrawerToggle.syncState(); |
| 363 | } |
| 364 | |
| 365 | @Override |
| 366 | public void onConfigurationChanged(Configuration newConfig) { |
| 367 | super.onConfigurationChanged(newConfig); |
| 368 | mDrawerToggle.onConfigurationChanged(newConfig); |
| 369 | } |
| 370 | |
| 371 | @Override |
| 372 | public boolean onOptionsItemSelected(MenuItem item) { |
| 373 | // Pass the event to ActionBarDrawerToggle, if it returns |
| 374 | // true, then it has handled the app icon touch event |
| 375 | if (mDrawerToggle.onOptionsItemSelected(item)) { |
| 376 | return true; |
| 377 | } |
| 378 | // Handle your other action bar items... |
| 379 | |
| 380 | return super.onOptionsItemSelected(item); |
| 381 | } |
| 382 | |
| 383 | ... |
| 384 | } |
| 385 | </pre> |
| 386 | |
| 387 | <p>For a complete example of a navigation drawer, download the sample available at the |
Scott Main | 0c39152 | 2013-05-15 14:38:16 -0700 | [diff] [blame] | 388 | <a href="#top">top of the page</a>.</p> |