DrawerLayout导航抽屉的使用

现在很多移动APP都支持那种左划或者右划调出抽屉选项的操作,目前第三方库比较知名的是配合ActionBarShelock(Github地址,官网地址)使用的SlidingMenu(Github地址),这个开源库很强大,也有很多APP都在使用,经过测试和验证过的,大家都可以放心使用

今天要说的是google官方出的DrawerLayout控件的使用,先看两张效果图:

根据Google官方文档可以知道,使用DrawerLayout控件一般需要下面这些步骤:

创建一个DrawerLayout布局

这个比较简单,要求DrawerLayout为根布局,第一个子布局代表没有打开抽屉时候的主视图(这个视图的宽度必须为match_parent),第二个子布局就代表抽屉布局了,一般抽屉中放入的都是一些设置选项,以ListView为常见布局.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<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">
<!-- 主视图布局,必须是DrawerLayout的第一个子布局,且宽度要充满父级容器 -->

<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" /&gt;
<!-- 抽屉视图布局 -->
<ListView
android:id="@+id/left_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="#c2e"/>
</android.support.v4.widget.DrawerLayout>

需要特别说明的是抽屉视图中的layout_gravity属性代表抽屉的打开方向,start代表从左边打开,end代表从右边打开,当然如果你的sdk api小于17的话,可以使用left和right可以达到同样的效果

初始化抽屉布局列表

抽屉列表的布局和内容取决于你自己实现的App的需求,但是一般都是以ListView配合Adapter来实现,这里我们用一个String数组来填充抽屉列表,部分代码如下:

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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
setListener();
}

private void setListener() {
mDrawerList.setAdapter(new ArrayAdapter&lt;&gt;(this, R.layout.drawer_list_item, mPlanetTitles));
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
}

private void findViews() {
mPlanetTitles = getResources().getStringArray(R.array.planets_array);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.left_drawer);
}
}

这一步很简单,就是一些初始化抽屉列表,填充数据,设置一下点击事件

处理抽屉栏里面不同选项的点击事件

我们知道,打开抽屉之后点击不同的选项要执行什么动作是取决于自己如何实现的,我这里是点击不同的选项就插入一个不同的fragment到主视图中,根据点击的不同行星名字我们在不同的fragment中显示对应行星的一张图片,首先看一下点击事件的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private class DrawerItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView&lt;?&gt; parent, View view, int position, long id) {
selectItem(position);
}
}

private void selectItem(int position) {
Fragment fragment = new PlanetFragment();
Bundle args = new Bundle();
args.putInt(PlanetFragment.ARG_PLANET_NUMBER,position);
fragment.setArguments(args);

// 通过替换已经存在的fragment来插入新的fragment
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame,fragment).commit();

// 高亮选中的item, 更新Title, 关闭抽屉视图
mDrawerList.setItemChecked(position,true);
setTitle(mPlanetTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
...

然后就是自定义的PlanetFragment的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PlanetFragment extends Fragment {
public static final String ARG_PLANET_NUMBER = "planet_number";

public PlanetFragment() {
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_planet, container, false);
int i = getArguments().getInt(ARG_PLANET_NUMBER);
String planet = getResources().getStringArray(R.array.planets_array)[i];
int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()), "drawable", getActivity().getPackageName());
((ImageView)view.findViewById(R.id.image)).setImageResource(imageId);
getActivity().setTitle(planet);
return view;
}
}

Fragment主要就是根据传递进来的不同的position来确定对应的行星的名字,然后通过名字来确定对应的imageId,从而在Fragment中展示出对应行星的图片。

监听抽屉视图的打开和关闭事件

为了监听抽屉的打开和关闭事件,我们可以调用DrawerLayout的setDrawerListener()传一个DrawerLayout.DrawerListener接口的实例对象进去,该接口提供回调事件像onDrawerOpened()和onDrawerClosed()

然后,与其实现DrawerListener接口,我们可以使用继承ActionBarDrawerToggle类的实例对象来代替,ActionBarDrawerToggle也实现了Drawer.DrawerListener,所以任然可以重写那些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
private class CustomActionBarDrawerToggle extends ActionBarDrawerToggle{

public CustomActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, int openDrawerContentDescRes, int closeDrawerContentDescRes) {
super(activity, drawerLayout, openDrawerContentDescRes, closeDrawerContentDescRes);
}

@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
getActionBar().setTitle(mDrawerTitle);
//刷新菜单,调用onPrepareOptionsMenu()
invalidateOptionsMenu();
}

@Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
getActionBar().setTitle(mTitle);
invalidateOptionsMenu();
}
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
...

然后设置监听

1
2
3
4
5
6
7
mDrawerToggle = new CustomActionBarDrawerToggle(
this,
mDrawerLayout,
R.string.drawer_open,
R.string.drawer_close
);
mDrawerLayout.setDrawerListener(mDrawerToggle);

这里需要说明的是如果使用support.v4包中的ActionBarDrawerToggle(已经不建议使用了),构造方法是这样的:

1
2
3
4
5
6
7
new ActionBarDrawerToggle(
this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
R.string.drawer_open, /* "open drawer" description for accessibility */
R.string.drawer_close /* "close drawer" description for accessibility */
)

需要传递一个R.drawable.ic_drawer导航抽屉图标来替换”上”符号,但是因为support.v4中的这个类已经不推荐使用了

1
2
This class is deprecated.
Please use ActionBarDrawerToggle in support-v7-appcompat.

推荐使用support.v7中的这个类,v7中该类有两个构造方法,如下所示:

1
2
3
4
5
//Construct a new ActionBarDrawerToggle.
ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, int openDrawerContentDescRes, int closeDrawerContentDescRes)

//Construct a new ActionBarDrawerToggle with a Toolbar.
ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, Toolbar toolbar, int openDrawerContentDescRes, int closeDrawerContentDescRes)

使用第一个就可以了,v7类中已经实现好很酷的抽屉按钮和动画.

通过app icon打开和关闭抽屉视图

1
2
3
//使ActionBar上的应用图标可以随着切换导航抽屉而改变    
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);

然后重写下面这些方法

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
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mDrawerToggle.syncState();
}

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

@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main,menu);
return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// The action bar home/up action should open or close the drawer.
// ActionBarDrawerToggle will take care of this.
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
// Handle action buttons
switch(item.getItemId()) {
case R.id.action_websearch:
// create intent to perform web search for this planet
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY, getActionBar().getTitle());
// catch event that there's no activity to handle intent
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
...

就这样就好了,其实还是挺简单的,但是到具体的app中根据需求怎么灵活运用就是另一回事了.

补充:有些时候你可能会发现执行下面这两行代码会报空指针

1
2
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);

这个和配置中android:theme有关系,改成下面这样类似的就可以了

1
android:theme="@android:style/Theme.Holo.Light.DarkActionBar"

完整代码Github地址:点击这里