今天这篇博客将记录一些关于DrawerLayout的基本用法,我想关于DrawerLayout的用法也许有不少不够了解,这也是比较正常的事情,因为DrawerLayout作为Android组件是Google后来在android中添加的,在android.support.v4包下。那么,DrawerLayout是一个怎么的组件呢?我们知道,当我们使用Android上各类App的时候,是不是注意过App主页上通常有一个“侧滑菜单”?关于侧滑菜单的实现,我在前面博客里有一些介绍,想多些了解的朋友请移步:
Android自定义控件——侧滑菜单
Android自定义控件——开源组件SlidingMenu的项目集成
这里用“网易新闻”客户端v4.4的截图来说明一下,这个DrawerLayout抽屉式布局是什么样子的。
好,大家已经看到了,网易新闻客户端效果很明显,当我们手指在屏幕左侧向右滑动时候,就会有一个抽屉式的菜单从左边弹出,并且是“悬浮”在主界面之上的,合理的利用了设备上有限的空间,同样手指在屏幕右侧向左滑动也会出现一个向左弹出的抽屉式菜单,用户体验效果还是不错的,在DrawerLayout出现之前,我们需要做侧滑菜单时,不得不自己实现一个或者使用Github上的开源的项目SlidingMenu,也许是Google也看到了SlidingMenu的强大之处,于是在Android的后期版本中添加了DrawerLayout来实现SlidingMenu同样功能的组件,而且为了兼容早期版本,将其添加在android,support.v4包下。
关于DrawerLayout的Training:http://developer.android.com/training/implementing-navigation/nav-drawer.html
关于DrawerLayout的API:http://developer.android.com/reference/android/support/v4/widget/DrawerLayout.html
另外,我已经翻译过了Google的Training课程,地址是:http://blog.csdn.net/allen315410/article/details/42875231
下面这个抽屉布局引用的是android.support.v4.DrawerLayout,类似于LineaLayout、RelativeLayout等布局一样定义,在DrawerLayout内部再定义3个布局,分别是管理主界面的FrameLayout,此布局用来展示界面切换的Fragment,下面是ListView,用来展示菜单列表,最后是一个RelativeLayout,用来展示右边的布局,布局代码如下:
这个布局文件示范了一些重要的布局特征.
正如上述所讲,因为DrawerLayout里包含一个ListView作为左边栏侧滑菜单,所以我们需要首先初始化这个抽屉列表,并且为这个列表适配上数据,数据适配器使用的是最简单的ArrayAdapter,模拟数据被简单的定义在res/values/strings.xml里,如下:
- Menu 1
- Menu 2
- Menu 3
- Menu 4
在Java代码中,首先创建一个MainActivity继承了android.support.v4.app.FragmentActivity,因为后续中需要进行Fragment之间的切换。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
......
// 初始化菜单列表
mMenuTitles = getResources().getStringArray(R.array.menu_array);
mMenuListView.setAdapter(new ArrayAdapter(this, R.layout.drawer_list_item, mMenuTitles));
mMenuListView.setOnItemClickListener(new DrawerItemClickListener());
......
}
当用户选择了抽屉列表里面的一个Item时, 系统调用onItemClickListener上的onItemClick(), 给setOnItemClickListener()你在onItemClick()方法里面做什么,在下面的例子中, 选择每一个Item都会在主要内容的布局中插入一个不同的Fragment.并且将导航列表的内容传递给Fragment中显示出来,下面是部分代码:
/**
* ListView上的Item点击事件
*
*/
private class DrawerItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
selectItem(position);
}
}
/**
* 切换主视图区域的Fragment
*
* @param position
*/
private void selectItem(int position) {
// TODO Auto-generated method stub
Fragment fragment = new ContentFragment();
Bundle args = new Bundle();
switch (position) {
case 0:
args.putString("key", mMenuTitles[position]);
break;
case 1:
args.putString("key", mMenuTitles[position]);
break;
case 2:
args.putString("key", mMenuTitles[position]);
break;
case 3:
args.putString("key", mMenuTitles[position]);
break;
default:
break;
}
fragment.setArguments(args); // FragmentActivity将点击的菜单列表标题传递给Fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
// 更新选择后的item和title,然后关闭菜单
mMenuListView.setItemChecked(position, true);
setTitle(mMenuTitles[position]);
mDrawerLayout.closeDrawer(mMenuListView);
}
细心的朋友也许会发现“网易新闻”v4.4客户端主页左上角上有个菜单“动态”的菜单按钮,显示流程是这样的,当菜单没有打开时,显示“三”这样的三条横线,当菜单打开(无论左右菜单)时,会显示“<-”这样的按钮,不停的变化,这样的效果是不是有点绚丽啊?!了解过Android5.0的朋友,应该会知道这种效果是使用了Android5.0新推出的Material Design设计语言做出来的效果,那么该怎么模仿这个效果呢?不好意思,由于偷懒,我已经在牛牛的Github中找到了这样的效果——material-menu组件,该组件模拟出了Android5.0下的Material Design效果,注意的是该组件中使用了JackWharton的NineOldAndroids动画效果。
material-menu主页:https://github.com/balysv/material-menu
NineOldAndroids主页:https://github.com/JakeWharton/NineOldAndroids
关于material-menu的使用可以参考其主页上的Demo和说明,集成时需要下载NineOldAndroids导出jar集成到项目中。下面是我使用的部分代码:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
......
// 设置抽屉打开时,主要内容区被自定义阴影覆盖
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
// 设置ActionBar可见,并且切换菜单和内容视图
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
mMaterialMenuIcon = new MaterialMenuIcon(this, Color.WHITE, Stroke.THIN);
mDrawerLayout.setDrawerListener(new DrawerLayout.SimpleDrawerListener() {
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
showView = drawerView;
if (drawerView == mMenuListView) {
mMaterialMenuIcon.setTransformationOffset(MaterialMenuDrawable.AnimationState.BURGER_ARROW, isDirection_left ? 2 - slideOffset : slideOffset);
} else if (drawerView == right_drawer) {
mMaterialMenuIcon.setTransformationOffset(MaterialMenuDrawable.AnimationState.BURGER_ARROW, isDirection_right ? 2 - slideOffset : slideOffset);
}
}
@Override
public void onDrawerOpened(android.view.View drawerView) {
if (drawerView == mMenuListView) {
isDirection_left = true;
} else if (drawerView == right_drawer) {
isDirection_right = true;
}
}
@Override
public void onDrawerClosed(android.view.View drawerView) {
if (drawerView == mMenuListView) {
isDirection_left = false;
} else if (drawerView == right_drawer) {
isDirection_right = false;
showView = mMenuListView;
}
}
});
......
}
此外,还需要关联一下meterial-menu的状态,需要覆盖Activity下的onPostCreate和onSaveInstanceState方法:
/**
* 根据onPostCreate回调的状态,还原对应的icon state
*/
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mMaterialMenuIcon.syncState(savedInstanceState);
}
/**
* 根据onSaveInstanceState回调的状态,保存当前icon state
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
mMaterialMenuIcon.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
为了尽量模拟出“网易新闻”v4.4客户端主页,我也在标题栏右上角添加一个小图标,为了能在点击这个小图标的时候弹出右边栏菜单,实现方式很简单,关于ActionBar上添加导航的知识可以在csdn上搜到一些解释或者上Android开发者官网查看源文档,我这里首先简单的在res/menu下main.xml中这样定义一个:
完成定义操作后,需要加载菜单布局:
/**
* 加载菜单
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
/**
* 点击ActionBar上菜单
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home:
if (showView == mMenuListView) {
if (!isDirection_left) { // 左边栏菜单关闭时,打开
mDrawerLayout.openDrawer(mMenuListView);
} else {// 左边栏菜单打开时,关闭
mDrawerLayout.closeDrawer(mMenuListView);
}
} else if (showView == right_drawer) {
if (!isDirection_right) {// 右边栏关闭时,打开
mDrawerLayout.openDrawer(right_drawer);
} else {// 右边栏打开时,关闭
mDrawerLayout.closeDrawer(right_drawer);
}
}
break;
case R.id.action_personal:
if (!isDirection_right) {// 右边栏关闭时,打开
if (showView == mMenuListView) {
mDrawerLayout.closeDrawer(mMenuListView);
}
mDrawerLayout.openDrawer(right_drawer);
} else {// 右边栏打开时,关闭
mDrawerLayout.closeDrawer(right_drawer);
}
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
这段的逻辑有点绕,事实上我做的是这样的,需要保证主界面上只能最多显示一个菜单布局,当左边的菜单布局展示时,此时打开右边菜单布局时,需要隐藏左边菜单布局;同样,如果右边的菜单布局已经在展示的时候,这时需要打开左边菜单布局,必须首先隐藏掉右边的菜单布局。为了判断当前即将显示或者关闭的是哪个布局,我在全局变量中定义了showView用来标记当前即将显示或者关闭的视图,如果showView==mMenuListView,说明左边菜单布局是即将被显示或隐藏的,这时进一步判断菜单是视图mMenuListView的是否已经显示的标记isDirection_left,来打开或者关闭左边视图菜单。
这里的逻辑似乎解释的有点乱,而且代码是分片段贴出来的,不利于理解,需要进一步理解的话,不妨继续看下面的部分,我已经贴出了所以的Java代码,注释也很详尽,可以方便理解,实在不行,还可以点击博客下方的下载链接,直接下载源码运行一下。
public class MainActivity extends FragmentActivity {
/** DrawerLayout */
private DrawerLayout mDrawerLayout;
/** 左边栏菜单 */
private ListView mMenuListView;
/** 右边栏 */
private RelativeLayout right_drawer;
/** 菜单列表 */
private String[] mMenuTitles;
/** Material Design风格 */
private MaterialMenuIcon mMaterialMenuIcon;
/** 菜单打开/关闭状态 */
private boolean isDirection_left = false;
/** 右边栏打开/关闭状态 */
private boolean isDirection_right = false;
private View showView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mMenuListView = (ListView) findViewById(R.id.left_drawer);
right_drawer = (RelativeLayout) findViewById(R.id.right_drawer);
this.showView = mMenuListView;
// 初始化菜单列表
mMenuTitles = getResources().getStringArray(R.array.menu_array);
mMenuListView.setAdapter(new ArrayAdapter(this,
R.layout.drawer_list_item, mMenuTitles));
mMenuListView.setOnItemClickListener(new DrawerItemClickListener());
// 设置抽屉打开时,主要内容区被自定义阴影覆盖
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow,
GravityCompat.START);
// 设置ActionBar可见,并且切换菜单和内容视图
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
mMaterialMenuIcon = new MaterialMenuIcon(this, Color.WHITE, Stroke.THIN);
mDrawerLayout.setDrawerListener(new DrawerLayoutStateListener());
if (savedInstanceState == null) {
selectItem(0);
}
}
/**
* ListView上的Item点击事件
*
*/
private class DrawerItemClickListener implements
ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView> parent, View view, int position,
long id) {
selectItem(position);
}
}
/**
* DrawerLayout状态变化监听
*/
private class DrawerLayoutStateListener extends
DrawerLayout.SimpleDrawerListener {
/**
* 当导航菜单滑动的时候被执行
*/
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
showView = drawerView;
if (drawerView == mMenuListView) {// 根据isDirection_left决定执行动画
mMaterialMenuIcon.setTransformationOffset(
MaterialMenuDrawable.AnimationState.BURGER_ARROW,
isDirection_left ? 2 - slideOffset : slideOffset);
} else if (drawerView == right_drawer) {// 根据isDirection_right决定执行动画
mMaterialMenuIcon.setTransformationOffset(
MaterialMenuDrawable.AnimationState.BURGER_ARROW,
isDirection_right ? 2 - slideOffset : slideOffset);
}
}
/**
* 当导航菜单打开时执行
*/
@Override
public void onDrawerOpened(android.view.View drawerView) {
if (drawerView == mMenuListView) {
isDirection_left = true;
} else if (drawerView == right_drawer) {
isDirection_right = true;
}
}
/**
* 当导航菜单关闭时执行
*/
@Override
public void onDrawerClosed(android.view.View drawerView) {
if (drawerView == mMenuListView) {
isDirection_left = false;
} else if (drawerView == right_drawer) {
isDirection_right = false;
showView = mMenuListView;
}
}
}
/**
* 切换主视图区域的Fragment
*
* @param position
*/
private void selectItem(int position) {
Fragment fragment = new ContentFragment();
Bundle args = new Bundle();
switch (position) {
case 0:
args.putString("key", mMenuTitles[position]);
break;
case 1:
args.putString("key", mMenuTitles[position]);
break;
case 2:
args.putString("key", mMenuTitles[position]);
break;
case 3:
args.putString("key", mMenuTitles[position]);
break;
default:
break;
}
fragment.setArguments(args); // FragmentActivity将点击的菜单列表标题传递给Fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragment).commit();
// 更新选择后的item和title,然后关闭菜单
mMenuListView.setItemChecked(position, true);
setTitle(mMenuTitles[position]);
mDrawerLayout.closeDrawer(mMenuListView);
}
/**
* 点击ActionBar上菜单
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home:
if (showView == mMenuListView) {
if (!isDirection_left) { // 左边栏菜单关闭时,打开
mDrawerLayout.openDrawer(mMenuListView);
} else {// 左边栏菜单打开时,关闭
mDrawerLayout.closeDrawer(mMenuListView);
}
} else if (showView == right_drawer) {
if (!isDirection_right) {// 右边栏关闭时,打开
mDrawerLayout.openDrawer(right_drawer);
} else {// 右边栏打开时,关闭
mDrawerLayout.closeDrawer(right_drawer);
}
}
break;
case R.id.action_personal:
if (!isDirection_right) {// 右边栏关闭时,打开
if (showView == mMenuListView) {
mDrawerLayout.closeDrawer(mMenuListView);
}
mDrawerLayout.openDrawer(right_drawer);
} else {// 右边栏打开时,关闭
mDrawerLayout.closeDrawer(right_drawer);
}
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
/**
* 根据onPostCreate回调的状态,还原对应的icon state
*/
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mMaterialMenuIcon.syncState(savedInstanceState);
}
/**
* 根据onSaveInstanceState回调的状态,保存当前icon state
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
mMaterialMenuIcon.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
/**
* 加载菜单
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
源码请在这里下载