0
点赞
收藏
分享

微信扫一扫

Android组件——使用DrawerLayout仿网易新闻v4.4侧滑菜单


概述

       今天这篇博客将记录一些关于DrawerLayout的基本用法,我想关于DrawerLayout的用法也许有不少不够了解,这也是比较正常的事情,因为DrawerLayout作为Android组件是Google后来在android中添加的,在android.support.v4包下。那么,DrawerLayout是一个怎么的组件呢?我们知道,当我们使用Android上各类App的时候,是不是注意过App主页上通常有一个“侧滑菜单”?关于侧滑菜单的实现,我在前面博客里有一些介绍,想多些了解的朋友请移步:

Android自定义控件——侧滑菜单

Android自定义控件——开源组件SlidingMenu的项目集成

      这里用“网易新闻”客户端v4.4的截图来说明一下,这个DrawerLayout抽屉式布局是什么样子的。

Android组件——使用DrawerLayout仿网易新闻v4.4侧滑菜单_Android

   

Android组件——使用DrawerLayout仿网易新闻v4.4侧滑菜单_ide_02

       好,大家已经看到了,网易新闻客户端效果很明显,当我们手指在屏幕左侧向右滑动时候,就会有一个抽屉式的菜单从左边弹出,并且是“悬浮”在主界面之上的,合理的利用了设备上有限的空间,同样手指在屏幕右侧向左滑动也会出现一个向左弹出的抽屉式菜单,用户体验效果还是不错的,在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


 

效果预览


Android组件——使用DrawerLayout仿网易新闻v4.4侧滑菜单_android_03


 


创建抽屉布局

      下面这个抽屉布局引用的是android.support.v4.DrawerLayout,类似于LineaLayout、RelativeLayout等布局一样定义,在DrawerLayout内部再定义3个布局,分别是管理主界面的FrameLayout,此布局用来展示界面切换的Fragment,下面是ListView,用来展示菜单列表,最后是一个RelativeLayout,用来展示右边的布局,布局代码如下:

 

[html]      view plain     copy               

        
    
     

        
    
   
1. <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"  
2. android:id="@+id/drawer_layout"  
3. android:layout_width="match_parent"  
4. android:layout_height="match_parent" >  
5.   
6. <FrameLayout  
7. android:id="@+id/content_frame"  
8. android:layout_width="match_parent"  
9. android:layout_height="match_parent" />  
10.   
11. <ListView  
12. android:id="@+id/left_drawer"  
13. android:layout_width="200dp"  
14. android:layout_height="match_parent"  
15. android:layout_gravity="start"  
16. android:background="#111"  
17. android:choiceMode="singleChoice"  
18. android:divider="@android:color/transparent"  
19. android:dividerHeight="0dp" />  
20.   
21. <RelativeLayout  
22. android:id="@+id/right_drawer"  
23. android:layout_width="220dp"  
24. android:layout_height="match_parent"  
25. android:layout_gravity="end"  
26. android:background="#111"  
27. android:gravity="center_horizontal" >  
28.   
29. <TextView  
30. android:layout_width="wrap_content"  
31. android:layout_height="wrap_content"  
32. android:text="这是右边栏"  
33. android:textColor="@android:color/white"  
34. android:textSize="24sp" />  
35. </RelativeLayout>  
36.   
37. </android.support.v4.widget.DrawerLayout>


这个布局文件示范了一些重要的布局特征.

 

  • 主要内容的视图(FrameLayout)必须是DrawLayout的第一个子元素, 因为导航抽屉是在主要内容视图的上面.
  • 主要内容视图设置为匹配父视图的宽度和高度, 因为它代表了整个界面导航抽屉是隐藏的.
  • 抽屉视图(ListView)必须指定其水平重力与android:layout_gravity属性。支持从右到左(RTL)语言,指定值与 "start" 代替 "left"(所以抽屉里出现在布局的右侧当布局是RTL时).这里将ListView设置为左边栏菜单,所以android:layout_gravity属性设置为“start”,将RelativeLayout设置为右边栏,设置android:layout_gravity属性为“end”.
  • 抽屉视图指定其宽度用dp单位和高度匹配父视图。抽屉里的宽度不能超过320 dp, 所以用户总是可以看到主要内容视图的一部分。

 

初始化抽屉列表

       正如上述所讲,因为DrawerLayout里包含一个ListView作为左边栏侧滑菜单,所以我们需要首先初始化这个抽屉列表,并且为这个列表适配上数据,数据适配器使用的是最简单的ArrayAdapter,模拟数据被简单的定义在res/values/strings.xml里,如下:

 


[html]      view plain     copy               

        
    
     

        
    
   
1. <string-array name="menu_array">  
2. <item>Menu 1</item>  
3. <item>Menu 2</item>  
4. <item>Menu 3</item>  
5. <item>Menu 4</item>  
6. </string-array>

       在Java代码中,首先创建一个MainActivity继承了android.support.v4.app.FragmentActivity,因为后续中需要进行Fragment之间的切换。

 

 


[java]  view plain copy

 


1. protected void onCreate(Bundle savedInstanceState) {  
2. super.onCreate(savedInstanceState);  
3.     setContentView(R.layout.activity_main);  
4.         ......  
5. // 初始化菜单列表  
6.     mMenuTitles = getResources().getStringArray(R.array.menu_array);  
7. new ArrayAdapter<String>(this, R.layout.drawer_list_item, mMenuTitles));  
8. new DrawerItemClickListener());  
9.         ......  
10. }


 

处理导航点击事件

      当用户选择了抽屉列表里面的一个Item时, 系统调用onItemClickListener上的onItemClick(), 给setOnItemClickListener()你在onItemClick()方法里面做什么,在下面的例子中, 选择每一个Item都会在主要内容的布局中插入一个不同的Fragment.并且将导航列表的内容传递给Fragment中显示出来,下面是部分代码:

 


[java]  view plain copy

 


 

1. /**
2.  * ListView上的Item点击事件
3.  * 
4.  */  
5. private class DrawerItemClickListener implements ListView.OnItemClickListener {  
6. @Override  
7. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {  
8.         selectItem(position);  
9.     }  
10. }  
11.   
12. /**
13.  * 切换主视图区域的Fragment
14.  * 
15.  * @param position
16.  */  
17. private void selectItem(int position) {  
18. // TODO Auto-generated method stub  
19. new ContentFragment();  
20. new Bundle();  
21. switch (position) {  
22. case 0:  
23. "key", mMenuTitles[position]);  
24. break;  
25. case 1:  
26. "key", mMenuTitles[position]);  
27. break;  
28. case 2:  
29. "key", mMenuTitles[position]);  
30. break;  
31. case 3:  
32. "key", mMenuTitles[position]);  
33. break;  
34. default:  
35. break;  
36.     }  
37. // FragmentActivity将点击的菜单列表标题传递给Fragment  
38.     FragmentManager fragmentManager = getSupportFragmentManager();  
39.     fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();  
40.   
41. // 更新选择后的item和title,然后关闭菜单  
42. true);  
43.     setTitle(mMenuTitles[position]);  
44.     mDrawerLayout.closeDrawer(mMenuListView);  
45. }

 

开源material-menu的集成

       细心的朋友也许会发现“网易新闻”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集成到项目中。下面是我使用的部分代码:

 


[java]  view plain copy

1. protected void onCreate(Bundle savedInstanceState) {  
2. super.onCreate(savedInstanceState);  
3.     setContentView(R.layout.activity_main);  
4.         ......  
5. // 设置抽屉打开时,主要内容区被自定义阴影覆盖  
6.     mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);  
7. // 设置ActionBar可见,并且切换菜单和内容视图  
8. true);  
9. true);  
10.   
11. new MaterialMenuIcon(this, Color.WHITE, Stroke.THIN);  
12.   
13. new DrawerLayout.SimpleDrawerListener() {  
14.   
15. @Override  
16. public void onDrawerSlide(View drawerView, float slideOffset) {  
17.             showView = drawerView;  
18. if (drawerView == mMenuListView) {  
19. 2 - slideOffset : slideOffset);  
20. else if (drawerView == right_drawer) {  
21. 2 - slideOffset : slideOffset);  
22.             }  
23.         }  
24.   
25. @Override  
26. public void onDrawerOpened(android.view.View drawerView) {  
27. if (drawerView == mMenuListView) {  
28. true;  
29. else if (drawerView == right_drawer) {  
30. true;  
31.             }  
32.         }  
33.   
34. @Override  
35. public void onDrawerClosed(android.view.View drawerView) {  
36. if (drawerView == mMenuListView) {  
37. false;  
38. else if (drawerView == right_drawer) {  
39. false;  
40.                 showView = mMenuListView;  
41.             }  
42.         }  
43.     });  
44.         ......  
45.   
46. }

此外,还需要关联一下meterial-menu的状态,需要覆盖Activity下的onPostCreate和onSaveInstanceState方法:

 

 


[java]  view plain copy

 


 

1. /**
2. * 根据onPostCreate回调的状态,还原对应的icon state
3. */  
4. @Override  
5. protected void onPostCreate(Bundle savedInstanceState) {  
6. super.onPostCreate(savedInstanceState);  
7.     mMaterialMenuIcon.syncState(savedInstanceState);  
8. }  
9.   
10. /**
11. * 根据onSaveInstanceState回调的状态,保存当前icon state
12. */  
13. @Override  
14. protected void onSaveInstanceState(Bundle outState) {  
15.     mMaterialMenuIcon.onSaveInstanceState(outState);  
16. super.onSaveInstanceState(outState);  
17. }

 

添加ActionBar上的菜单按钮

      为了尽量模拟出“网易新闻”v4.4客户端主页,我也在标题栏右上角添加一个小图标,为了能在点击这个小图标的时候弹出右边栏菜单,实现方式很简单,关于ActionBar上添加导航的知识可以在csdn上搜到一些解释或者上Android开发者官网查看源文档,我这里首先简单的在res/menu下main.xml中这样定义一个:

 


[html]  view plain copy

 


 

1. <menu xmlns:android="http://schemas.android.com/apk/res/android" >  
2.   
3. <item  
4. android:id="@+id/action_personal"  
5. android:icon="@drawable/action_personal"  
6. android:orderInCategory="100"  
7. android:showAsAction="always"  
8. android:title="@string/action_personal"/>  
9.   
10. </menu>

完成定义操作后,需要加载菜单布局:

 

 


[java]  view plain copy

 


 

1. /**
2. * 加载菜单
3. */  
4. @Override  
5. public boolean onCreateOptionsMenu(Menu menu) {  
6. // Inflate the menu; this adds items to the action bar if it is present.  
7.     getMenuInflater().inflate(R.menu.main, menu);  
8. return true;  
9. }

 

 

标题栏导航点击事件处理


[java]  view plain copy

 


 

1. /**
2. * 点击ActionBar上菜单
3. */  
4. @Override  
5. public boolean onOptionsItemSelected(MenuItem item) {  
6. int id = item.getItemId();  
7. switch (id) {  
8. case android.R.id.home:  
9. if (showView == mMenuListView) {  
10. if (!isDirection_left) { // 左边栏菜单关闭时,打开  
11.                 mDrawerLayout.openDrawer(mMenuListView);  
12. else {// 左边栏菜单打开时,关闭  
13.                 mDrawerLayout.closeDrawer(mMenuListView);  
14.             }  
15. else if (showView == right_drawer) {  
16. if (!isDirection_right) {// 右边栏关闭时,打开  
17.                 mDrawerLayout.openDrawer(right_drawer);  
18. else {// 右边栏打开时,关闭  
19.                 mDrawerLayout.closeDrawer(right_drawer);  
20.             }  
21.         }  
22. break;  
23. case R.id.action_personal:  
24. if (!isDirection_right) {// 右边栏关闭时,打开  
25. if (showView == mMenuListView) {  
26.                 mDrawerLayout.closeDrawer(mMenuListView);  
27.             }  
28.             mDrawerLayout.openDrawer(right_drawer);  
29. else {// 右边栏打开时,关闭  
30.             mDrawerLayout.closeDrawer(right_drawer);  
31.         }  
32. break;  
33. default:  
34. break;  
35.     }  
36. return super.onOptionsItemSelected(item);  
37. }

      这段的逻辑有点绕,事实上我做的是这样的,需要保证主界面上只能最多显示一个菜单布局,当左边的菜单布局展示时,此时打开右边菜单布局时,需要隐藏左边菜单布局;同样,如果右边的菜单布局已经在展示的时候,这时需要打开左边菜单布局,必须首先隐藏掉右边的菜单布局。为了判断当前即将显示或者关闭的是哪个布局,我在全局变量中定义了showView用来标记当前即将显示或者关闭的视图,如果showView==mMenuListView,说明左边菜单布局是即将被显示或隐藏的,这时进一步判断菜单是视图mMenuListView的是否已经显示的标记isDirection_left,来打开或者关闭左边视图菜单。
      同样的道理,如果当前即将显示或者隐藏的是右边导航菜单的话,我们需要进一步判断右边导航是否已经显示,从而进行相关打开或隐藏的决定。

      这里的逻辑似乎解释的有点乱,而且代码是分片段贴出来的,不利于理解,需要进一步理解的话,不妨继续看下面的部分,我已经贴出了所以的Java代码,注释也很详尽,可以方便理解,实在不行,还可以点击博客下方的下载链接,直接下载源码运行一下。

 

全部源码


[java]  view plain copy

 


 

1. public class MainActivity extends FragmentActivity {  
2.   
3. /** DrawerLayout */  
4. private DrawerLayout mDrawerLayout;  
5. /** 左边栏菜单 */  
6. private ListView mMenuListView;  
7. /** 右边栏 */  
8. private RelativeLayout right_drawer;  
9. /** 菜单列表 */  
10. private String[] mMenuTitles;  
11. /** Material Design风格 */  
12. private MaterialMenuIcon mMaterialMenuIcon;  
13. /** 菜单打开/关闭状态 */  
14. private boolean isDirection_left = false;  
15. /** 右边栏打开/关闭状态 */  
16. private boolean isDirection_right = false;  
17. private View showView;  
18.   
19. @Override  
20. protected void onCreate(Bundle savedInstanceState) {  
21. super.onCreate(savedInstanceState);  
22.         setContentView(R.layout.activity_main);  
23.   
24.         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);  
25.         mMenuListView = (ListView) findViewById(R.id.left_drawer);  
26.         right_drawer = (RelativeLayout) findViewById(R.id.right_drawer);  
27. this.showView = mMenuListView;  
28.   
29. // 初始化菜单列表  
30.         mMenuTitles = getResources().getStringArray(R.array.menu_array);  
31. new ArrayAdapter<String>(this,  
32.                 R.layout.drawer_list_item, mMenuTitles));  
33. new DrawerItemClickListener());  
34.   
35. // 设置抽屉打开时,主要内容区被自定义阴影覆盖  
36.         mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow,  
37.                 GravityCompat.START);  
38. // 设置ActionBar可见,并且切换菜单和内容视图  
39. true);  
40. true);  
41.   
42. new MaterialMenuIcon(this, Color.WHITE, Stroke.THIN);  
43. new DrawerLayoutStateListener());  
44.   
45. if (savedInstanceState == null) {  
46. 0);  
47.         }  
48.   
49.     }  
50.   
51. /**
52.      * ListView上的Item点击事件
53.      * 
54.      */  
55. private class DrawerItemClickListener implements  
56.             ListView.OnItemClickListener {  
57. @Override  
58. public void onItemClick(AdapterView<?> parent, View view, int position,  
59. long id) {  
60.             selectItem(position);  
61.         }  
62.     }  
63.   
64. /**
65.      * DrawerLayout状态变化监听
66.      */  
67. private class DrawerLayoutStateListener extends  
68.             DrawerLayout.SimpleDrawerListener {  
69. /**
70.          * 当导航菜单滑动的时候被执行
71.          */  
72. @Override  
73. public void onDrawerSlide(View drawerView, float slideOffset) {  
74.             showView = drawerView;  
75. if (drawerView == mMenuListView) {// 根据isDirection_left决定执行动画  
76.                 mMaterialMenuIcon.setTransformationOffset(  
77.                         MaterialMenuDrawable.AnimationState.BURGER_ARROW,  
78. 2 - slideOffset : slideOffset);  
79. else if (drawerView == right_drawer) {// 根据isDirection_right决定执行动画  
80.                 mMaterialMenuIcon.setTransformationOffset(  
81.                         MaterialMenuDrawable.AnimationState.BURGER_ARROW,  
82. 2 - slideOffset : slideOffset);  
83.             }  
84.         }  
85.   
86. /**
87.          * 当导航菜单打开时执行
88.          */  
89. @Override  
90. public void onDrawerOpened(android.view.View drawerView) {  
91. if (drawerView == mMenuListView) {  
92. true;  
93. else if (drawerView == right_drawer) {  
94. true;  
95.             }  
96.         }  
97.   
98. /**
99.          * 当导航菜单关闭时执行
100.          */  
101. @Override  
102. public void onDrawerClosed(android.view.View drawerView) {  
103. if (drawerView == mMenuListView) {  
104. false;  
105. else if (drawerView == right_drawer) {  
106. false;  
107.                 showView = mMenuListView;  
108.             }  
109.         }  
110.     }  
111.   
112. /**
113.      * 切换主视图区域的Fragment
114.      * 
115.      * @param position
116.      */  
117. private void selectItem(int position) {  
118. new ContentFragment();  
119. new Bundle();  
120. switch (position) {  
121. case 0:  
122. "key", mMenuTitles[position]);  
123. break;  
124. case 1:  
125. "key", mMenuTitles[position]);  
126. break;  
127. case 2:  
128. "key", mMenuTitles[position]);  
129. break;  
130. case 3:  
131. "key", mMenuTitles[position]);  
132. break;  
133. default:  
134. break;  
135.         }  
136. // FragmentActivity将点击的菜单列表标题传递给Fragment  
137.         FragmentManager fragmentManager = getSupportFragmentManager();  
138.         fragmentManager.beginTransaction()  
139.                 .replace(R.id.content_frame, fragment).commit();  
140.   
141. // 更新选择后的item和title,然后关闭菜单  
142. true);  
143.         setTitle(mMenuTitles[position]);  
144.         mDrawerLayout.closeDrawer(mMenuListView);  
145.     }  
146.   
147. /**
148.      * 点击ActionBar上菜单
149.      */  
150. @Override  
151. public boolean onOptionsItemSelected(MenuItem item) {  
152. int id = item.getItemId();  
153. switch (id) {  
154. case android.R.id.home:  
155. if (showView == mMenuListView) {  
156. if (!isDirection_left) { // 左边栏菜单关闭时,打开  
157.                     mDrawerLayout.openDrawer(mMenuListView);  
158. else {// 左边栏菜单打开时,关闭  
159.                     mDrawerLayout.closeDrawer(mMenuListView);  
160.                 }  
161. else if (showView == right_drawer) {  
162. if (!isDirection_right) {// 右边栏关闭时,打开  
163.                     mDrawerLayout.openDrawer(right_drawer);  
164. else {// 右边栏打开时,关闭  
165.                     mDrawerLayout.closeDrawer(right_drawer);  
166.                 }  
167.             }  
168. break;  
169. case R.id.action_personal:  
170. if (!isDirection_right) {// 右边栏关闭时,打开  
171. if (showView == mMenuListView) {  
172.                     mDrawerLayout.closeDrawer(mMenuListView);  
173.                 }  
174.                 mDrawerLayout.openDrawer(right_drawer);  
175. else {// 右边栏打开时,关闭  
176.                 mDrawerLayout.closeDrawer(right_drawer);  
177.             }  
178. break;  
179. default:  
180. break;  
181.         }  
182. return super.onOptionsItemSelected(item);  
183.     }  
184.   
185. /**
186.      * 根据onPostCreate回调的状态,还原对应的icon state
187.      */  
188. @Override  
189. protected void onPostCreate(Bundle savedInstanceState) {  
190. super.onPostCreate(savedInstanceState);  
191.         mMaterialMenuIcon.syncState(savedInstanceState);  
192.     }  
193.   
194. /**
195.      * 根据onSaveInstanceState回调的状态,保存当前icon state
196.      */  
197. @Override  
198. protected void onSaveInstanceState(Bundle outState) {  
199.         mMaterialMenuIcon.onSaveInstanceState(outState);  
200. super.onSaveInstanceState(outState);  
201.     }  
202.   
203. /**
204.      * 加载菜单
205.      */  
206. @Override  
207. public boolean onCreateOptionsMenu(Menu menu) {  
208. // Inflate the menu; this adds items to the action bar if it is present.  
209.         getMenuInflater().inflate(R.menu.main, menu);  
210. return true;  
211.     }  
212.   
213. }

举报

相关推荐

0 条评论