好,大家已经看到了,网易新闻客户端效果很明显,当我们手指在屏幕左侧向右滑动时候,就会有一个抽屉式的菜单从左边弹出,并且是“悬浮”在主界面之上的,合理的利用了设备上有限的空间,同样手指在屏幕右侧向左滑动也会出现一个向左弹出的抽屉式菜单,用户体验效果还是不错的,在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://www.jb51.net/article/102142.htm
效果预览
创建抽屉布局
下面这个抽屉布局引用的是android.support.v4.DrawerLayout,类似于LineaLayout、RelativeLayout等布局一样定义,在DrawerLayout内部再定义3个布局,分别是管理主界面的FrameLayout,此布局用来展示界面切换的Fragment,下面是ListView,用来展示菜单列表,最后是一个RelativeLayout,用来展示右边的布局,布局代码如下:
<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" > <FrameLayoutandroid:id="@+id/content_frame"android:layout_width="match_parent"android:layout_height="match_parent" /> <ListViewandroid:id="@+id/left_drawer"android:layout_width="200dp"android:layout_height="match_parent"android:layout_gravity="start"android:background="#111"android:choiceMode="singleChoice"android:divider="@android:color/transparent"android:dividerHeight="0dp" /> <RelativeLayoutandroid:id="@+id/right_drawer"android:layout_width="220dp"android:layout_height="match_parent"android:layout_gravity="end"android:background="#111"android:gravity="center_horizontal" > <TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="这是右边栏"android:textColor="@android:color/white"android:textSize="24sp" /></RelativeLayout></android.support.v4.widget.DrawerLayout>这个布局文件示范了一些重要的布局特征.
<string-array name="menu_array"><item>Menu 1</item><item>Menu 2</item><item>Menu 3</item><item>Menu 4</item> </string-array>在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<String>(this, R.layout.drawer_list_item, mMenuTitles));mMenuListView.setOnItemClickListener(new DrawerItemClickListener());...... }处理导航点击事件
/*** ListView上的Item点击事件**/ private class DrawerItemClickListener implements ListView.OnItemClickListener {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {selectItem(position);} }/*** 切换主视图区域的Fragment** @param position*/ private void selectItem(int position) {// TODO Auto-generated method stubFragment 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将点击的菜单列表标题传递给FragmentFragmentManager fragmentManager = getSupportFragmentManager();fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit(); // 更新选择后的item和title,然后关闭菜单mMenuListView.setItemChecked(position, true);setTitle(mMenuTitles[position]);mDrawerLayout.closeDrawer(mMenuListView); }开源material-menu的集成
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() { @Overridepublic 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);}} @Overridepublic void onDrawerOpened(android.view.View drawerView) {if (drawerView == mMenuListView) {isDirection_left = true;} else if (drawerView == right_drawer) {isDirection_right = true;}} @Overridepublic 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); }添加ActionBar上的菜单按钮
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <itemandroid:id="@+id/action_personal"android:icon="@drawable/action_personal"android:orderInCategory="100"android:showAsAction="always"android:title="@string/action_personal"/></menu>完成定义操作后,需要加载菜单布局:
/** * 加载菜单 */ @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,来打开或者关闭左边视图菜单。
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; @Overrideprotected 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<String>(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 implementsListView.OnItemClickListener {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position,long id) {selectItem(position);}} /*** DrawerLayout状态变化监听*/private class DrawerLayoutStateListener extendsDrawerLayout.SimpleDrawerListener {/*** 当导航菜单滑动的时候被执行*/@Overridepublic 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);}} /*** 当导航菜单打开时执行*/@Overridepublic void onDrawerOpened(android.view.View drawerView) {if (drawerView == mMenuListView) {isDirection_left = true;} else if (drawerView == right_drawer) {isDirection_right = true;}} /*** 当导航菜单关闭时执行*/@Overridepublic 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将点击的菜单列表标题传递给FragmentFragmentManager fragmentManager = getSupportFragmentManager();fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit(); // 更新选择后的item和title,然后关闭菜单mMenuListView.setItemChecked(position, true);setTitle(mMenuTitles[position]);mDrawerLayout.closeDrawer(mMenuListView);} /*** 点击ActionBar上菜单*/@Overridepublic 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*/@Overrideprotected void onPostCreate(Bundle savedInstanceState) {super.onPostCreate(savedInstanceState);mMaterialMenuIcon.syncState(savedInstanceState);} /*** 根据onSaveInstanceState回调的状态,保存当前icon state*/@Overrideprotected void onSaveInstanceState(Bundle outState) {mMaterialMenuIcon.onSaveInstanceState(outState);super.onSaveInstanceState(outState);} /*** 加载菜单*/@Overridepublic 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;}}源码请在这里下载:http://xiazai.jb51.net/201701/yuanma/AndroidDrawerLayout(jb51.net).rar