概述:
Drawer导航栏是用于显示主要导航的控件, 它出现在屏幕的左侧. 大多数时候是隐藏的, 当用户从左侧滑入屏幕或者点击App bar上的图标, 它才会显示. 在Google App中它非常的常用, 也是Android上非常推崇的一种导航方式.
规格:
如上图, 包括四个部分:
1. 名字: Roboto Medium, 14sp, #FFFFFF.
2. Email: Roboto Regular, 14sp,#FFFFFF.
3. 列表项: Roboto Medium, 14sp, 87% #000000.
4. 副标题: Roboto Medium, 14sp, 54% #000000. 对其到16dp关键线.
内容:
高度: Drawer会占满屏幕的高度, 但是会位于状态栏后方, 位于状态栏后面的部分依然可见, 但是会变暗. 下图是它显示出来的样子.
选中的状态: 在一个条目被选中之后, 该条目会变成符合app主颜色的颜色(或者 #000000 100%)来表示自己被选中了. 选中项的所在行会被高亮.
分隔线: 所有在drawer中的分隔线都是铺满整个drawer的, 并且在其上方和下方都有8dp的填充.
上图第一个是分隔线的样子, 第二个是它所占空间的大小. 这里有8个dp的填充.
Drawer行为:
永久显示(桌面系统默认推荐使用): 这种情况下drawer永久显示在屏幕左侧而不能被隐藏. 包括三种风格: 全高度drawer, 在appbar下面的drawer以及悬浮的drawer, 较少使用.
持续导航: 这种drawer可以切换显示和隐藏状态. 默认情况下隐藏, 当选择菜单图标的时候才显示, 并且当用户手动关闭它之前会持续显示. 这种导航可以应用于所有比手机大的屏幕上.
持续导航的迷你模式: 这种模式下, 持续导航改变了它的宽度. 它的隐藏状态会以一个迷你drawer的形式固定在屏幕左边, 并被app bar盖住. 当扩展时它跟标准的drawer一样. 适用于那些需要快速访问的app.下图左边是它的隐藏状态, 右图是显示状态.
临时导航: 这种drawer可以在显示和隐藏间切换, 默认情况下隐藏, 打开的时候会临时覆盖其它的内容, 直到用户选择其中的一个条目. 推荐使用: 平板. 必须使用: 手机. 通常在手机上见到的应该是这种.
Drawer的使用:
下面介绍如何使用支持库中的DrawerLayout API来实现一个drawer导航.
创建一个Drawerlayout:
要想使用drawer layout, 得先在布局中使用DrawerLayout对象作为layout的根元素. 然后在其中添加一个view作为主界面的内容, 也就是当drawer隐藏的时候显示的基础layout, 之后再添加另一个view作为drawer显示的时候的内容. 栗如, 下面这个layout使用了一个DrawerLayout, 并且包含了两个子view: 一个FrameLayout作为主界面, 还有一个ListView作为导航drawer.
<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">
<!-- The maincontent view -->
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!-- Thenavigation drawer -->
<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="#111"/>
</android.support.v4.widget.DrawerLayout>
这里有几个很重要的地方需要注意:
1. 主界面的view(也就是栗子中的FrameLayout)必须是DrawerLayout中的第一个子view(XML order impliesz-ordering and the drawer must be on top of the content).
2. 主界面的view必须设置为填满parent的长和宽, 因为它是drawer隐藏的时候全部的界面.
3. Drawer view(上栗中的ListView)必须用android:layout_gravity属性指定它的横向重力. 为了支持从右到左读的语言(RTL),指定该值为”start”而不是”left”(这样的话当layout是RTL的时候, drawer就可以从右边显示出来了).
4. Drawer view需要使用dp作为单位来指定它的宽度, 并且高度是填满parent的. Drawer的宽度不应该超过320dp,这样用户就可以总能看到主界面的一部分了.
初始化DrawerList:
在我们的activity中, 首先要完成的事情就是初始化drawer的list. 如何实现取决于app的内容, 但是drawer经常跟ListView配合使用, list应该通过一个Adapter初始化(比如ArrayAdapter或者SimpleCursorAdapter). 栗子:
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()方法来接收drawer的list中的点击事件.
处理导航点击事件:
当用户选择了drawer中list的一项时, 系统会调用OnItemClickListener中的onItemClick(). 在该方法中的逻辑取决于app内容. 下面的栗子中, 选择一个条目会插入一个Fragment到主界面的view中.
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);
}
监听Drawer的显示和隐藏事件:
要监听drawer的显示和隐藏事件, 需要在DrawerLayout上调用setDrawerListener(), 并传递给它DrawerLayout.DrawerListener的实现. 该接口提供了可以监听drawer事件的接口, 比如onDrawerOpened()和onDrawerClosed(),分别对应drawer的显示和隐藏.
但是如果我们的app还包含action bar, 那么我们可以使用ActionBarDrawerToggle类代替DrawerLayout.DrawerListener. ActionBarDrawerToggle类实现了DrawerLayout.DrawerListener接口, 所以我们还是可以使用这些方法, 使用该类还有利于action bar图标跟drawer的交互(下一小节讨论).
我们应该在drawer显示的时候修改action bar的内容, 比如修改title和移除action item. 下面的代码演示了如何通过ActionBarDrawerToggle使用DrawerLayout.DrawerListener接口中的回调方法:
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);
}
}
使用APP图标来显示或隐藏Drawer:
用户可以通过从左到右的滑动手势来显示drawer, 但是如果我们同时还使用了action bar, 那么我们应该允许用户通过点击action bar上的app图标来显示或隐藏drawer. 并且app图标应该用一个专用的图标来提醒用户drawer的存在. 我们可以使用ActionBarDrawerToggle来实现这个功能. 想要这个类能按照我们的想法工作, 需要用构造方法来创建一个它的对象, 该方法需要这些参数:
1. Drawer所在的Activity.
2. DrawerLayout.
3. 一个drawable资源作为drawer的指示器. 标准的drawer图标可以在这里找到.
4. 一个String资源用于描述”显示drawer”操作.
5. 一个String资源用于描述”隐藏drawer”操作.
然后, 不管我们是否创建一个ActionBarDrawerToggle的子类作为drawer的监听器, 都得在activity的生命周期中调用一些ActionBarDrawerToggle的方法:
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);
}
...
}
总结:
Drawer使用四步:
1. 定义DrawerLayout. 包括正文和Drawer本身.
2. 初始化Drawer.
3. 处理点击事件.
4. 处理ActionBar和Drawer的关联.
参考: https://developer.android.com/training/implementing-navigation/nav-drawer.html
https://www.google.com/design/spec/patterns/navigation-drawer.html