相信
Sliding Menu
很多人都用过,在
Android
和
iOS
的
app
中,越来越多的开发者都会把自己的菜单界面放在一个列表里,然后让用户通过向右(或者向左)滑动的操作看到应用所有的功能。
Google
官方的应用也基本都选择了这种交互方式,不同的是,
Google
使用的是
Navigation Drawer
,而我们大部分用的还是
Sliding Menu
。
大家对
Sliding Menu
这个开源项目可能已经很熟悉了,但是
Navigation Drawer
我们有些童鞋可能了解的还比较少,它是
Google I/O 2013
刚推出不久的一个在
support v4
包里面的一个控件,下面我就通过一个
demo
跟大家介绍一下
Navigation Drawer
的使用方法。
创建一个抽屉
导航抽屉是一个位于屏幕左侧边缘用来显示应用程序导航项的一个面板。导航抽屉在大部分时间是不显示的,但两种情况下会进行显示:一是发生从屏幕左侧边缘向右滑的手势,二是点击了工具栏中应用图标。导航抽屉在
SupportLibrary
中提供支持,在使用导航抽屉时,需要符合导航抽屉设计原则(
NavigationDrawer
),看看你是否有必要创建导航抽屉
。
创建抽屉布局
如果你要添加一个导航抽屉,需要用
DrawerLayout
来作为用户界面的根视图,
DrawerLayout
视图下需放置两个子视图,一个是用来显示显示屏幕的主体内容(导航抽屉隐藏的时候),一个是用来显示导航抽屉。
用来显示屏幕主体内容的视图一般是
FrameLayout
(运行的时候,会被一个
Fragment
填充),用来显示导航抽屉的视图一般是一个
ListView
,如下所示
<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 main content view --> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- The navigation 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
、显示主体内容的视图必须是
DrawerLayout
下的第一个子视图,因为抽屉视图必须在主体内容视图的上方(意味着
DrawerLayout
是一个以
z
轴来布局的控件)
2
、显示主体内容的视图必须设置为匹配父视图的高和宽,因为当抽屉视图隐藏的时候显示主体内容的视图代表了整个用户界面
3
、抽屉视图的
layout_gravity
属性值需为
“start”
,
To support right-to-left (RTL) languages,specify the value with "start" insteadof "left" (so the drawer appears on the right when thelayout is RTL)
4
、抽屉视图的宽度不宜匹配父视图,应当适当的窄一点,这样就能在抽屉显示的时候还能看到主体内容视图的一部分
初始化抽屉列表
抽屉视图一般包含一个
ListView
(具体包含的
View
取决于你的应用),该
ListView
和平常没什么两样,都需要一个
Adapter
来填充,也可设置单项选中监听器
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()); ... } }
复制代码
处理导航项选点击事件
导航项的点击事件其实就是包含的
ListView
项的点击事件,我们需要根据点击的项来相应的改变主体内容,记得上面说过显示主体内容的
View
运行时一般会是一个
Fragment
,所以只要把当前的
Fragment
替换成相应的
Fragment
就行了
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); }
复制代码
监听导航抽屉打开和关闭事件
但是如果你的
Activity
包含
Action Bar
话,你可以选择
ActionBarDrawerToggle
来替代
DrawerListener ,ActionBarDrawerToggle
实现了
DrawerListener
接口,所以抽屉的打开和关闭事件照样能监听的到,并且使用
ActionBarDrawerToggle
能促进
Action Bar Icon
和导航抽屉之间的交互(通过点击
icon
来打开和关闭导航抽屉),当导航抽屉打开或关闭时
Action Bar
的状态也应该做相应的改变(
NavigationDrawer
中有介绍)
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) { getActionBar().setTitle(mTitle); invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } /** Called when a drawer has settled in a completely open state. */ public void onDrawerOpened(View 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); } }
复制代码
点击应用图标来打开和关闭导航抽屉
前面我们是介绍过通过手势来打开和关闭导航抽屉,但是如果
Activity
包含
Action Bar
的话,当我们点击应用图标时也能打开和关闭导航抽屉,而且我们也需要通过图标来指示导航抽屉当前的状态,如果我们是使用
ActionBarDrawerToggle
类的话,可以通过设置构造方法的参数来做到这一点,它的构造方法参数有五个,分别代表:宿主
Activity
、
DrawerLayout
、导航抽屉打开时应用图标、导航抽屉打开时描述文本、导航抽屉关闭时描述文本
还有一点要注意的是,当手机屏幕的配置环境发生变化时(横竖屏切换),导航抽屉的配置也需改变,当宿主
Activity
的
onRestoreInstanceState
方法调用的时候,导航抽屉的状态也需进行同步,可在
onPostCreate
方法中进行同步,具体的可以看如下代码
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) { getActionBar().setTitle(mTitle); } /** Called when a drawer has settled in a completely open state. */ public void onDrawerOpened(View 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); } ... }
复制代码
几张运行的截图:
源码下载: