原文地址 http://developer.android.com/training/implementing-navigation/nav-drawer.html

创建抽屉导航

导航抽屉是在 屏幕左侧边缘的 应用主导航选项的面板. 它大多数时间是隐藏的, 但当用户用手指从屏幕的左侧滑动, 或者当用户点击应用顶部工具栏的应用图标的时候, 它就会显示.

此课程是描述 怎样有效的使用在Support Library中的DrawLayout接口 去实现一个导航抽屉.

创建一个抽屉布局

添加一个抽屉, 声明UI的时候需要吧DrawLayout作为你的布局文件的根视图(root view). 在DrawLayout里面, 添加一个主要内容的视图(当抽屉导航隐藏的时候你的主要的布局文件), 和另一个包含导航抽屉的视图.

举例来说, 下面的布局使用了DrawLayout, 它有2个子视图: 一个FrameLayout包含主要的内容, 一个ListView的导航抽屉.

  1. <android.support.v4.widget.DrawerLayout

  2. xmlns:android="http://schemas.android.com/apk/res/android"

  3. android:id="@+id/drawer_layout"

  4. android:layout_width="match_parent"

  5. android:layout_height="match_parent">

  6. <FrameLayout

  7. android:id="@+id/content_frame"

  8. android:layout_width="match_parent"

  9. android:layout_height="match_parent"/>

  10. <ListViewandroid:id="@+id/left_drawer"

  11. android:layout_width="240dp"

  12. android:layout_height="match_parent"

  13. android:layout_gravity="start"

  14. android:choiceMode="singleChoice"

  15. android:divider="@android:color/transparent"

  16. android:dividerHeight="0dp"

  17. android:background="#111"/>

  18. android.support.v4.widget.DrawerLayout>

这个布局文件示范了一些重要的布局特征.



  • 主要内容的视图(FrameLayout)必须是DrawLayout的第一个子元素, 因为导航抽屉是在主要内容视图的上面.

  • 主要内容视图设置为匹配父视图的宽度和高度, 因为它代表了整个界面导航抽屉是隐藏的.

  • 抽屉视图(ListView)必须指定其水平重力与android:layout_gravity属性。支持从右到左(RTL)语言,指定值与"start"代替"left"(所以抽屉里出现在布局的右侧当布局是RTL时).

  • 抽屉视图指定其宽度用dp单位和高度匹配父视图。抽屉里的宽度不能超过320 dp, 所以用户总是可以看到主要内容视图的一部分.


初始化抽屉列表

在你的Activity中, 第一件事就是初始化导航抽屉列表里面的元素, 你如何做取决于你的应用程序的内容,但一个导航抽屉通常包括一个ListView, 因此清单应该由一个Adapter填充(例如ArrayAdapter或SimpleCursorAdapter).

例如, 这里演示了如何用String array来初始化一个导航列表.

  1. publicclass MainActivity extends Activity {  

  2. private String[] mPlanetTitles;  

  3. private ListView mDrawerList;  

  4.    ...  

  5. @Override

  6. publicvoid onCreate(Bundle savedInstanceState) {  

  7. super.onCreate(savedInstanceState);  

  8.        setContentView(R.layout.activity_main);  

  9.        mPlanetTitles = getResources().getStringArray(R.array.planets_array);  

  10.        mDrawerList = (ListView) findViewById(R.id.left_drawer);  

  11. // Set the adapter for the list view

  12.        mDrawerList.setAdapter(new ArrayAdapter(this,  

  13.                R.layout.drawer_list_item, mPlanetTitles));  

  14. // Set the list's click listener

  15.        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());  

  16.        ...  

  17.    }  

  18. }  

这个 代码调用setOnItemClickListener()去接收导航抽屉列表的点击事件. 下一节将展示如何实现这个接口,当用户选择一个Item时改变内容视图.


处理导航点击事件

当用户选择了抽屉列表里面的一个Item时, 系统调用onItemClickListener上的onItemClick(), 给setOnItemClickListener().

你在onItemClick()方法里面做什么, 取决于你的app实现的结构. 在下面的例子中, 选择每一个Item都会在主要内容的布局中插入一个不同的Fragment.

  1. privateclass DrawerItemClickListener implements ListView.OnItemClickListener {  

  2. @Override

  3. publicvoid onItemClick(AdapterView parent, View view, int position, long id) {  

  4.        selectItem(position);  

  5.    }  

  6. }  

  7. /** Swaps fragments in the main content view */

  8. privatevoid selectItem(int position) {  

  9. // Create a new fragment and specify the planet to show based on position

  10.    Fragment fragment = new PlanetFragment();  

  11.    Bundle args = new Bundle();  

  12.    args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);  

  13.    fragment.setArguments(args);  

  14. // Insert the fragment by replacing any existing fragment

  15.    FragmentManager fragmentManager = getFragmentManager();  

  16.    fragmentManager.beginTransaction()  

  17.                   .replace(R.id.content_frame, fragment)  

  18.                   .commit();  

  19. // Highlight the selected item, update the title, and close the drawer

  20.    mDrawer.setItemChecked(position, true);  

  21.    setTitle(mPlanetTitles[position]);  

  22.    mDrawerLayout.closeDrawer(mDrawer);  

  23. }  

  24. @Override

  25. publicvoid setTitle(CharSequence title) {  

  26.    mTitle = title;  

  27.    getActionBar().setTitle(mTitle);  

  28. }  


监听打开和关闭事件


侦听抽屉打开和关闭事件,调用你的DrawerLayout setDrawerListener(), 并将其传递给DrawerLayout.DrawerListener的实现. 这个接口提供了回调抽屉事件, 如onDrawerOpened()和onDrawerClosed ()。

然而, 相对于实现DrawerLayout.DrawerListener, 如果你的Activity包括工具栏, 可以代替继承ActionBarDrawerToggle类. ActionBarDrawerToggle实现了DrawerLayout.DrawerListener. 所以你仍然可以覆盖这些回调, 但它也有助于正确的交互行为, 在工具栏的图标和导航抽屉之间(下一节将进一步讨论)。

就像在导航抽屉设计指南一样,当抽屉是可见的时候, 你应该修改工具栏的内容, 如改变标题和删除操作Item. 下面的代码用ActionBarDrawerToggle类的一个实例, 显示了如何重写DrawerLayout.DrawerListener的回调方法:

  1. publicclass MainActivity extends Activity {  

  2. private DrawerLayout mDrawerLayout;  

  3. private ActionBarDrawerToggle mDrawerToggle;  

  4. private CharSequence mDrawerTitle;  

  5. private CharSequence mTitle;  

  6.    ...  

  7. @Override

  8. publicvoid onCreate(Bundle savedInstanceState) {  

  9. super.onCreate(savedInstanceState);  

  10.        setContentView(R.layout.activity_main);  

  11.        ...  

  12.        mTitle = mDrawerTitle = getTitle();  

  13.        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);  

  14.        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,  

  15.                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {  

  16. /** Called when a drawer has settled in a completely closed state. */

  17. publicvoid onDrawerClosed(View view) {  

  18.                getActionBar().setTitle(mTitle);  

  19.                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()

  20.            }  

  21. /** Called when a drawer has settled in a completely open state. */

  22. publicvoid onDrawerOpened(View drawerView) {  

  23.                getActionBar().setTitle(mDrawerTitle);  

  24.                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()

  25.            }  

  26.        };  

  27. // Set the drawer toggle as the DrawerListener

  28.        mDrawerLayout.setDrawerListener(mDrawerToggle);  

  29.    }  

  30. /* Called whenever we call invalidateOptionsMenu() */

  31. @Override

  32. publicboolean onPrepareOptionsMenu(Menu menu) {  

  33. // If the nav drawer is open, hide action items related to the content view

  34. boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);  

  35.        menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);  

  36. returnsuper.onPrepareOptionsMenu(menu);  

  37.    }  

  38. }  


下一节描述ActionBarDrawerToggle构造函数参数和其他所需的步骤来设置它来处理工具栏的图标.

打开关闭应用图标

用户可以打开和关闭导航抽屉, 通过手指从屏幕左侧的边缘滑动, 但如果你使用工具栏, 你应该也能允许用户打开和关闭它, 通过触摸应用程序图标. 应用程序图标也可以显示一个特殊的图标关于导航抽屉的状态. 你可以实现所有这些行为通过使用ActionBarDrawerToggle, 如前一节所示。

让ActionBarDrawerToggle工作, 创建一个它的实例用它的构造方法, 这就需要以下参数:


  • 持有抽屉的Activity.

  • DrawerLayout对象.

  • 一个Drawable资源作为抽屉指示器.

  • 字符串资源描述"打开抽屉"动作.

  • 字符串资源描述"关闭抽屉"动作.


然后, 无论你是否已经创建了一个ActionBarDrawerToggle的子类作为你的抽屉的Listener, 你需要在几个Activity生命周期的地方, 调用你的ActionBarDrawerToggle:

  1. publicclass MainActivity extends Activity {  

  2. private DrawerLayout mDrawerLayout;  

  3. private ActionBarDrawerToggle mDrawerToggle;  

  4.    ...  

  5. publicvoid onCreate(Bundle savedInstanceState) {  

  6.        ...  

  7.        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);  

  8.        mDrawerToggle = new ActionBarDrawerToggle(  

  9. this,                  /* host Activity */

  10.                mDrawerLayout,         /* DrawerLayout object */

  11.                R.drawable.ic_drawer,  /* nav drawer icon to replace 'Up' caret */

  12.                R.string.drawer_open,  /* "open drawer" description */

  13.                R.string.drawer_close  /* "close drawer" description */

  14.                ) {  

  15. /** Called when a drawer has settled in a completely closed state. */

  16. publicvoid onDrawerClosed(View view) {  

  17.                getActionBar().setTitle(mTitle);  

  18.            }  

  19. /** Called when a drawer has settled in a completely open state. */

  20. publicvoid onDrawerOpened(View drawerView) {  

  21.                getActionBar().setTitle(mDrawerTitle);  

  22.            }  

  23.        };  

  24. // Set the drawer toggle as the DrawerListener

  25.        mDrawerLayout.setDrawerListener(mDrawerToggle);  

  26.        getActionBar().setDisplayHomeAsUpEnabled(true);  

  27.        getActionBar().setHomeButtonEnabled(true);  

  28.    }  

  29. @Override

  30. protectedvoid onPostCreate(Bundle savedInstanceState) {  

  31. super.onPostCreate(savedInstanceState);  

  32. // Sync the toggle state after onRestoreInstanceState has occurred.

  33.        mDrawerToggle.syncState();  

  34.    }  

  35. @Override

  36. publicvoid onConfigurationChanged(Configuration newConfig) {  

  37. super.onConfigurationChanged(newConfig);  

  38.        mDrawerToggle.onConfigurationChanged(newConfig);  

  39.    }  

  40. @Override

  41. publicboolean onOptionsItemSelected(MenuItem item) {  

  42. // Pass the event to ActionBarDrawerToggle, if it returns

  43. // true, then it has handled the app icon touch event

  44. if (mDrawerToggle.onOptionsItemSelected(item)) {  

  45. returntrue;  

  46.        }  

  47. // Handle your other action bar items...

  48. returnsuper.onOptionsItemSelected(item);  

  49.    }  

  50.    ...  

  51. }