原文链接:https://developer.android.com/training/implementing-navigation/nav-drawer.html
导航抽屉(Navigation Drawer)是位于屏幕左边缘用于展示程序主导航选项的一个面板。大部分时间,它是被隐藏的。当手指从屏幕的左边缘滑过时,或在应用程序的顶层,当用户点击操作栏(Action Bar)中的程序图标时,它会被显示出来。
在你决定在你的程序中使用 Navigation Drawer 之前,你应当了解其用例和设计原则,详细内容请查阅 Navigation Drawer 设计指南
为了添加一个 Navigation Drawer,在声明用户界面时,使用 DrawerLayout 作为布局的根视图。在 DrawerLayout 中添加一个视图用于存放屏幕的主要内容(当抽屉被隐藏时,你的主要布局),添加另一个视图用于存放 Navigation Drawer 的内容。
举个例子,下面的布局使用了一个 DrawerLayout,它包含了两个子视图:一个 FrameLayout 用来存放主要内容和一个用于存放 Navigation Drawer 的 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">
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<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>
这个布局展示了一些重要的布局特点:
在你的 activity 中,一个你首先要做的事是初始化导航抽屉列表中的每一个选项。你如何来做取决于你的程序的内容,通常一个导航抽屉由一个 ListView 构成,因此这个列表应当有一个适配器(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(this,
R.layout.drawer_list_item, mPlanetTitles));
// Set the list's click listener
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
...
}
}
当用户选中了抽屉列表中的一个选项时,系统会调用 OnItemClickListener 的 onItemClick()。
在 onItemClick() 中你应该做什么取决于你的程序逻辑。在下面的例子中,选中列表中的每一个选项都会向主要内容视图(FrameLayout,元素 ID 是 R.id.content_frame)插入一个不同的 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);
}
想要鉴定抽屉的打开和关闭事件,我们需要调用 setDrawerListener() 并传入一个实现了 DrawerLayout.DrawerListener 接口的对象。这个接口提供了抽屉事件的回调,例如 onDrawerOpened() 和 onDrawerClosed。
当你的 activity 包含 action bar 时,你可以继承 ActionBarDrawerToggle 类代替实现 DrawerLayout.DrawerListener 接口。ActionBarDrawerToggle 实现了 DrawerLayout.DrawerListener 接口,因此你仍然可以重写那些回调函数,这样可以促成 action bar 图标与导航抽屉之间正确的交互行为。
正如导航抽屉设计指南中讨论的,当抽屉可见时,你应当修改 action bar 的内容,例如修改标题和移除功能选项,标题和功能选项都是和主要内容相关联的。下面的代码展示了在一个 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);
}
}
用户可以通过从左边缘或向左边缘的滑动手势打开或者关闭导航抽屉。但是如果你正在使用 action bar,你应该允许用户通过触摸程序图标的方式打开或关闭导航抽屉。当抽屉存在时,程序图标应该变为一个特殊的图标。你可以使用 ActionBarDrawerToggle 来实现这些行为。
创建一个 ActionBarDrawerToggle 对象,你需要如下的参数( android.support.v4.app.ActionBarDrawerToggle):
- 托管抽屉的 Activity 对象
- DrawerLayout 对象
- 用于指示抽屉显示的绘制资源(drawable resource)
- 字符串资源,用来形容“打开抽屉”的动作
- 字符串资源,用来形容“关闭抽屉”的动作
对于android.support.v7.app.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);
}
...
}