源码获取方式
- 打开Android Studio,选择 File-New-Import Sample-Navigation Drawer
- 或者直接github:https://github.com/googlesamples/android-NavigationDrawer
主要功能
实现侧边导航栏,平时是隐藏的,通过点击特定按钮或者从屏幕边缘滑动可以打开导航抽屉。
效果图:
UI布局
根视图是一个DrawerLayout。
子视图第一个必须是界面的主内容(即导航隐藏时显示的内容),这是因为导航栏在打开时是要遮盖主内容的,所以主内容必须放在导航栏的前面。Demo中是一个FragmentLayout,高宽都设为match_parent,已达到填充整个页面的效果。
其后可以包含一到两个视图,对应左右两侧的导航栏。Demo中只有一个左侧导航栏视图,用的是RecyclerView。Google 建议导航栏的宽度不超过320dp,已保证导航打开时主内容还是部分可见的,Demo中设置为240dp 。这里必须指定android:layout_gravity属性,设置为start代码从左向右滑出,end则代表从右向左滑出。
Activity代码如下:
<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:openDrawer="start" >
<!-- As the main content view, the view below consumes the entire
space available using match_parent in both dimensions. -->
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- android:layout_gravity="start" tells DrawerLayout to treat
this as a sliding drawer on the left side for left-to-right
languages and on the right side for right-to-left languages.
The drawer is given a fixed width in dp and extends the full height of
the container. A solid background is used for contrast
with the content view. -->
<android.support.v7.widget.RecyclerView
android:id="@+id/left_drawer"
android:scrollbars="vertical"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left|start"
android:choiceMode="singleChoice"
android:divider="@null"
app:layoutManager="LinearLayoutManager"
/>
</android.support.v4.widget.DrawerLayout>
原理分析
Demo中DrawerLayout相关的功能主要有:
* 通过顶部工具栏按钮打开或关闭导航抽屉
* 通过侧滑打开或关闭导航抽屉
* 通过监听导航栏状态,变更顶部工具栏的标题,显示或隐藏搜索按钮
通过按钮开关抽屉
NavigationDrawerActivity继承自Activity,所以自带一个ActionBar。
首先使能ActionBar上的App图标,使其显示并能被点击:
// enable ActionBar app icon to behave as action to toggle nav drawer
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
通过创建ActionBarDrawerToggle建立ActionBar和Drawer的关联,ActionBarDrawerToggle的构造函数包含5个参数,分别是主Activity、DrawerLayout对象、工具栏图标替换、打开描述、关闭描述。
创建后还要通过mDrawerLayout.setDrawerListener(mDrawerToggle)进行绑定。
代码如下:
// ActionBarDrawerToggle ties together the the proper interactions
// between the sliding drawer and the action bar app icon
mDrawerToggle = 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 */
) {
public void onDrawerClosed(View view) {
getActionBar().setTitle(mTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
public void onDrawerOpened(View drawerView) {
getActionBar().setTitle(mDrawerTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};
mDrawerLayout.setDrawerListener(mDrawerToggle);
if (savedInstanceState == null) {
selectItem(0);
}
}
并且onPostCreate和onConfigurationChanged中添加语句,代码如下:
/**
* When using the ActionBarDrawerToggle, you must call it during
* onPostCreate() and onConfigurationChanged()...
*/
@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);
// Pass any configuration change to the drawer toggls
mDrawerToggle.onConfigurationChanged(newConfig);
}
这样就实现了更改ActionBar中的图标,并且点击图标可以打开或关闭导航抽屉。
另外Demo中为实现ActionBar中搜索按钮的功能,重写了onOptionsItemSelected函数,因此需要作特殊处理,已保证导航栏打开关闭功能的正常。
在onOptionsItemSelected中,通过if (mDrawerToggle.onOptionsItemSelected(item))判断如果是点击的是App图标,则直接返回。
代码如下:
@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);
}
}
通过侧滑打开或关闭导航
当导航抽屉位于左侧时,在左侧屏幕边缘向右滑动,导航抽屉会打开,打开后向左滑动或者点击主内容未被遮挡部分,导航抽屉会关闭。当导航抽屉位于右侧时则相反。
这些功能全部在DrawerLayout类中实现,使用时不需要做特殊处理。
通过抽屉视图的gravity来设置导航的位置和打开方向,设置为start则导航在左,从左到右打开,设置为end则相反。
android:layout_gravity="start"
也可以打开或关闭手势滑动
mDrawer_layout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); //关闭手势滑动
mDrawer_layout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); //打开手势滑动
监听导航栏状态
Demo中会监听导航栏是开启还是关闭,实现了以下效果:打开导航时,标题设置为Navigation Drawer,隐藏搜索按钮;关闭导航时,设置标题为选中的item,显示搜索按钮。
标题更改在ActionBarDrawerToggle的构造函数中通过重写onDrawerClosed(View view)和onDrawerOpened(View drawerView)实现:
public void onDrawerClosed(View view) {
getActionBar().setTitle(mTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
public void onDrawerOpened(View drawerView) {
getActionBar().setTitle(mDrawerTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
搜索按钮的显示和隐藏通过重写onPrepareOptionsMenu(Menu menu)实现:
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);
}
扩展
Google这个Demo的原始代码中的部分类已经被废弃了,我对Demo进行了少量修改,将v4.app.ActionBarDrawerToggle 替换为 v7.app.ActionBarDrawerToggle。 同时增加了一个右侧抽屉,并且在toolbar增加两个按钮来控制左右抽屉。
下载地址:
https://github.com/zhongchenyu/DrawerDemo
参考
Creating a Navigation Drawer:
https://developer.android.com/training/implementing-navigation/nav-drawer.html#Init
DrawerLayout API文档:
https://developer.android.com/reference/android/support/v4/widget/DrawerLayout.html