Android之侧滑菜单DrawerLayout

  • 目前侧滑菜单已广泛应用于各大应用,这一菜单形式简洁明了,深受用户的喜爱。在谷歌官方推出 DrawerLayout 之前,开发者大都通过 SlidingMenu 开源库来实现这一功能。而现在 DrawerLayout 被谷歌包含在了 android-support-v4.jar 这个包下,可以让开发者更简单方便的实现侧滑菜单这一功能。
  • Google I/O 2013Android更新了Support库,Support Library包中实现了侧滑菜单效果的控件---drawerLayout,支持创建 Navigation Drawer(导航抽屉)模式。
  • drawerLayout其实是一个布局控件,跟LinearLayout等控件是一种东西,但是drawerLayout带有滑动的功能。只要按照drawerLayout的规定布局方式写完布局,就能有侧滑的效果。
  • drawerLayout分为侧边菜单和主内容区两部分,侧边菜单可以根据手势展开与隐藏(drawerLayout自身特性),主内容区的内容可以随着菜单的点击而变化(这需要使用者自己实现)。这个东西主要的原理就是左边是一个ListView(抽屉菜单),右边是一个FrameLayout.(用来显示页面的主要内容)。
  • 在需要抽屉菜单的界面,用DrawerLayout 作为界面根控件。在DrawerLayout里面第一个View为当前界面主内容;第二个和第三个View为抽屉菜单内容。如果当前界面只需要一个抽屉菜单,则第三个View可以省略
  • 具体可参考以下链接,官方的开发页是:
    Android Navigation Drawer Design :
    http://developer.android.com/design/patterns/navigation-drawer.html
    Android Navigation Drawer 教程:
    https://developer.android.com/training/implementing-navigation/nav-drawer.html(包含官方Demo的实例 大家也可以自己下载看一下)

接下来附上完整的drawerLayout的例子,来源于官方网站的demo

Activity:


package com.example.android.navigationdrawerexample;
import java.util.Locale;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.SearchManager;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends Activity {
 private DrawerLayout mDrawerLayout;
 private ListView mDrawerList;
 private ActionBarDrawerToggle mDrawerToggle;
 private CharSequence mDrawerTitle;
 private CharSequence mTitle;
 private String[] mPlanetTitles;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mTitle = mDrawerTitle = getTitle();
 mPlanetTitles = getResources().getStringArray(R.array.planets_array);
 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
 mDrawerList = (ListView) findViewById(R.id.left_drawer);
 // set a custom shadow that overlays the main content when the drawer opens
 mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
 // set up the drawer's list view with items and click listener
 mDrawerList.setAdapter(new ArrayAdapter<String>(this,
 R.layout.drawer_list_item, mPlanetTitles));
 mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
 // enable ActionBar app icon to behave as action to toggle nav drawer
 getActionBar().setDisplayHomeAsUpEnabled(true);
 getActionBar().setHomeButtonEnabled(true);
 // ActionBarDrawerToggle ties together the the proper interactions
 // between the sliding drawer and the action bar app icon
 mDrawerToggle = new ActionBarDrawerToggle(
 this, /* host Activity */
 mDrawerLayout, /* DrawerLayout object */
 R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
 R.string.drawer_open, /* "open drawer" description for accessibility */
 R.string.drawer_close /* "close drawer" description for accessibility */
 ) {
 public void onDrawerClosed(View view) {
 getActionBar().setTitle(mTitle);
 invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
 }
 public void onDrawerOpened(View drawerView) {
 getActionBar().setTitle(mDrawerTitle);
 invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
 }
 };
 mDrawerLayout.setDrawerListener(mDrawerToggle);
 if (savedInstanceState == null) {
 selectItem(0);
 }
 }
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
 MenuInflater inflater = getMenuInflater();
 inflater.inflate(R.menu.main, menu);
 return super.onCreateOptionsMenu(menu);
 }
 /* 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);
 }
 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
 // The action bar home/up action should open or close the drawer.
 // ActionBarDrawerToggle will take care of this.
 if (mDrawerToggle.onOptionsItemSelected(item)) {
 return true;
 }
 // Handle action buttons
 switch(item.getItemId()) {
 case R.id.action_websearch:
 // create intent to perform web search for this planet
 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
 intent.putExtra(SearchManager.QUERY, getActionBar().getTitle());
 // catch event that there's no activity to handle intent
 if (intent.resolveActivity(getPackageManager()) != null) {
 startActivity(intent);
 } else {
 Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();
 }
 return true;
 default:
 return super.onOptionsItemSelected(item);
 }
 }
 /* The click listner for ListView in the navigation drawer */
 private class DrawerItemClickListener implements ListView.OnItemClickListener {
 @Override
 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
 selectItem(position);
 }
 }
 private void selectItem(int position) {
 // update the main content by replacing fragments
 Fragment fragment = new PlanetFragment();
 Bundle args = new Bundle();
 args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
 fragment.setArguments(args);
 FragmentManager fragmentManager = getFragmentManager();
 fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
 // update selected item and title, then close the drawer
 mDrawerList.setItemChecked(position, true);
 setTitle(mPlanetTitles[position]);
 mDrawerLayout.closeDrawer(mDrawerList);
 }
 @Override
 public void setTitle(CharSequence title) {
 mTitle = title;
 getActionBar().setTitle(mTitle);
 }
 /**
 * When using the ActionBarDrawerToggle, you must call it during
 * onPostCreate() and onConfigurationChanged()...
 */
 @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);
 // Pass any configuration change to the drawer toggls
 mDrawerToggle.onConfigurationChanged(newConfig);
 }
 /**
 * Fragment that appears in the "content_frame", shows a planet
 */
 public static class PlanetFragment extends Fragment {
 public static final String ARG_PLANET_NUMBER = "planet_number";
 public PlanetFragment() {
 // Empty constructor required for fragment subclasses
 }
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
 Bundle savedInstanceState) {
 View rootView = inflater.inflate(R.layout.fragment_planet, container, false);
 int i = getArguments().getInt(ARG_PLANET_NUMBER);
 String planet = getResources().getStringArray(R.array.planets_array)[i];
 int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()),
 "drawable", getActivity().getPackageName());
 ((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId);
 getActivity().setTitle(planet);
 return rootView;
 }
 }
}

Xml
activity_main.xml


<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>

fragment_planet.xml


<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/image"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#000000"
 android:gravity="center"
 android:padding="32dp" />

drawer_list_item.xml


<TextView xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@android:id/text1"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:textAppearance="?android:attr/textAppearanceListItemSmall"
 android:gravity="center_vertical"
 android:paddingLeft="16dp"
 android:paddingRight="16dp"
 android:textColor="#fff"
 android:background="?android:attr/activatedBackgroundIndicator"
 android:minHeight="?android:attr/listPreferredItemHeightSmall"/>

注意的点:

1. activity_main.xml中主内容区的布局代码要放在侧滑菜单布局的前面,这可以帮助DrawerLayout判断谁是侧滑菜单,谁是主内容区;侧滑菜单的部分的布局(这里是ListView)可以设置layout_gravity属性,如果为"start" 即为从左往右调用菜单,如果为"end" 即为从右往左调用菜单。

2. 我们看到很多使用drawerLayout的代码中都同时使用了Fragment,这会造成误解,以为使用drawerLayout必须用到Fragment,其实这是错误的,使用Fragment是因为在侧滑菜单被点击的时候,主内容区如果内容比较复杂,用Fragment去填充会更容易,如果你的主内容区只是一个简单的字符串,只想在不同菜单点击的时候更新一下字符串的内容,我觉得没必要用Fragment。不过官方的例子其实中,Fragment所做的就是更新字符串内容这么简单。

**3.侧边菜单其实只是一个普通的View,一般里面装的是ListView,看起来就像菜单,他完全可以是一个button,textView等等。虽然称为菜单,但跟Activity的菜单形式是两码事,Activity的菜单只需要在资源文件中定义好,就能按照固定的形式显示出来。而drawerLayout的侧边菜单显示成什么样完全是取决于你自己,同样点击事件也完全由你自己去写。在点击侧边菜单选项的时候我们往往需要隐藏菜单来显示整个菜单对应的内容。DrawerLayout.closeDrawer方法用于隐藏侧边菜单,DrawerLayout.openDrawer方法用于展开侧边菜单。如下代码 **

mDrawerList.setAdapter(new ArrayAdapter<String>(this,
 R.layout.drawer_list_item, mPlanetTitles));
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
/* The click listner for ListView in the navigation drawer */
private class DrawerItemClickListener implements ListView.OnItemClickListener {
 @Override
 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
 selectItem(position);
 }
}
private void selectItem(int position) {
 // update the main content by replacing fragments
 Fragment fragment = new PlanetFragment();
 Bundle args = new Bundle();
 args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
 fragment.setArguments(args);
 FragmentManager fragmentManager = getFragmentManager();
 fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
 // update selected item and title, then close the drawer
 mDrawerList.setItemChecked(position, true);
 setTitle(mPlanetTitles[position]);
 mDrawerLayout.closeDrawer(mDrawerList);
}

4. view注意事项:

  • 显示界面主要内容的View (上面的 FrameLayout ) 必须为DrawerLayout的第一个子View, 原因在于 XML 布局文件中的View顺序为Android系统中的 z-ordering顺序,而抽屉必须出现在内容之上。
  • 显示界面内容的View宽度和高度设置为和父View一样,原因在于当抽屉菜单不可见的时候,界面内容代表整个界面UI。
  • 抽屉菜单 (上面的 ListView) 必须使用android:layout_gravity属性设置水平的 gravity值 .如果要支持 right-to-left (RTL,从右向左阅读)语言 用 "start" 代替 "left" (当在 RTL语言运行时候,菜单出现在右侧)。
  • **抽屉菜单的宽度为 dp 单位而高度和父View一样。抽屉菜单的宽度应该不超过320dp,这样用户可以在菜单打开的时候看到部分内容界面。 **
注意事项.jpg

最后,如果这篇文章对您有用处,就给个喜欢吧~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容