抽屉式导航栏是显示在屏幕的左边缘,它是应用程序的主导航选项面板。它大部分时间是处于隐藏状态的,但是当用户从屏幕的左边缘挥动手指时它就会显示出来,而在应用程序的顶层,用户触摸操作栏上的应用程序图标也可以将其显示出来。

本课程介绍在可用的API 支持库下如何实现导航抽屉DrawerLayout。

首先我们可以看一下最终的效果图:

DrawerLayout效果图
DrawerLayout效果图

1、创建一个抽屉布局文件(Drawer Layout)

要添加一个抽屉式导航,首先你必须要声明你的用户界面的根布局为DrawerLayout对象。在DrawerLayout里面,添加一个主视图内容的view对象(当抽屉被隐藏的时候显示在屏幕上的 视图)和另外一个包含抽屉导航视图的view对象。

例如,下面的布局采用了包含两个子视图的DrawerLayout:一个是FrameLayout,包含了主要内容(在运行时由填充Fragment),和一个ListView的导航抽屉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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" >

<!-- 主视图 -->
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<!-- 抽屉视图 -->
<ListView
android:id="@+id/left_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#111"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp" />

</android.support.v4.widget.DrawerLayout>

这个布局文件演示了一些比较重要的布局特点,如下:

  • 主内容视图(上面的FrameLayout)必须是DrawerLayout布局对象的第一个子view对象,这是因为xml文件意味着是z排序(即空间上的上下排序,也就是说抽屉导航栏应该位于住内容视图的垂直上方)。
  • 主内容视图的view对象的两个属性:layout_width、layout_height必须是match_parent的,这是因为当抽屉导航栏被隐藏的时候他便是整个UI。
  • 抽屉视图要指定其宽度的单位为dp,高度与父视图相匹配。抽屉的宽度应该不超过320dp,这样用户可以随时看到主要内容视图的一些部分。
  • 抽屉视图(ListView)必须用 android:layout_gravity 属性来指定他的水平重力方向。为了支持从右到左(RTL)的语言,应该要指定其属性为”start” ,而不是”left” 。

2、初始化抽屉列表

在你的Activity中,首先要做的事之一就是初始化抽屉导航栏中的列表项。至于你要如何做取决于你的应用程序中的内容,但是通常情况下抽屉导航栏都是包含了一个ListView,所以列表应该由Adapter来填充(如ArrayAdapter或者SimpleCursorAdapter)。

例如下面将告诉你如何用字符串数组初始化抽屉导航栏列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainActivity extends Activity {
private String[] mPlanetTitles;
private DrawerLayout mDrawerLayout;
private ListView mDrawerList;
...

@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<String>(this,
R.layout.drawer_list_item, mPlanetTitles));
// Set the list's click listener
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

...
}
}

此代码还调用setOnItemClickListener()来接收抽屉导航栏列表的点击事件。下一节将展示如何实现此接口,以实现当用户选择一个项目后更改主内容视图。

3、处理导航的点击事件

当用户选择在抽屉导航栏里列表中的项目时,系统调用OnItemClickListener接口中的onItemClick()方法以返回给OnItemClickListener() 。你要在onItemClick()方法中做什么样的处理取决于你如何实现你的的应用程序结构。在下面的例子中,你将可以实现下面的内容:当点击抽屉导航栏中列表项里的item时,将会在住内容视图中插入一个不同的Fragment。(FrameLayout的id为 R.id.content_frame)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private class DrawerItemClickListener implements ListView.OnItemClickListener {
@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);
}

@Override
public void setTitle(CharSequence title) {
mTitle = title;
getActionBar().setTitle(mTitle);
}

4、监听打开以及关闭抽屉导航栏的事件

要监听抽屉打开和关闭事件,在你的DrawerLayout中调用setDrawerListener()并传入一个DrawerLayout.DrawerListener接口 。此接口为抽屉导航栏提供了回掉方法,如onDrawerOpened()和onDrawerClosed()。

然而,除了实现DrawerLayout.DrawerListener接口之外,如果你的Activity包含了ActionBar,你也可以继承ActionBarDrawerToggle这个类,这是因为ActionBarDrawerToggle类实现DrawerLayout.DrawerListener接口,所以你仍然可以复写这些方法,但是这对ActionBar图标和抽屉的交互也有一定的帮助。

正如在抽屉导航设计指南讨论的一样,当抽屉处于可见状态时你应该要修改操作栏(ActionBar)的内容,例如你应该要改变标题,并移除与主内容试图相关的列表项。下面的代码演示了如何通过实例化ActionBarDrawerToggle类并复写在DrawerLayout.DrawerListener中的回调方法,以达到这样的目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MainActivity extends Activity {
private DrawerLayout mDrawerLayout;
private ActionBarDrawerToggle mDrawerToggle;
private CharSequence mDrawerTitle;
private CharSequence mTitle;
...

@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) {
super.onDrawerClosed(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) {
super.onDrawerOpened(drawerView);
getActionBar().setTitle(mDrawerTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};

// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
}

/* Called whenever we call invalidateOptionsMenu() */
@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);
}
}

下面将介绍ActionBarDrawerToggle构造函数的参数,并设置它来处理与操作栏图标交互所需的其他步骤。

5、如何通过应用程序图标打开或关闭抽屉导航栏

用户可以通过来自或朝向屏幕的左边缘轻扫的手势来打开与关闭抽屉式导航栏,但如果你正在使用操作栏(ActionBar) ,你也应该允许用户通过触摸应用程序图标的方式打开或者关闭抽屉导航栏。而应用程序图标也应用一个特殊的图标注明抽屉的存在。你也可以通过实现上一节讲到的ActionBarDrawerToggle来实现这些行为。

为了使ActionBarDrawerToggle发挥作用,你将需要创建它的一个实例与它的构造方法,这需要下列参数: 

  • 持有该抽屉的Activity
  • 一个DrawerLayout
  • 用于作为抽屉指标的绘制资源(drawable resource),这里会提供一个android官方提供的图标包
  • 用来形容“打开抽屉”这一操作的字符串资源
  • 用来形容“关闭抽屉”这一操作的字符串资源

最后,不管你是否已经创建了ActionBarDrawerToggle的子类作为抽屉的监听器,你还是需要在整个Activity的生命周期中的几个地方调用ActionBarDrawerToggle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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) {
super.onDrawerClosed(view);
getActionBar().setTitle(mTitle);
}

/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
getActionBar().setTitle(mDrawerTitle);
}
};

// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);

getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
}

@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig);
}

@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);
}

...
}

教程的最后,我提供了项目供你下载,希望这个教程能够帮到你。