分类:Android UI 整理 830人阅读 评论(3) 收藏 举报 SlidingMenu ANDROID

前一段时间写了一篇Android小教程 滑动菜单 SlidingMenu 实现方法 (一)今天拓展一下,实现一个可以实现菜单页联动的SlidingMenu。

其实SlidingMenu的实现方实在太多太多,今天的实现思路在主体上也和上一篇文章中有所不同,尽量和大家分享多一些方法。另外功能上也更丰富一下。

先来解释一下我们要实现的效果:

联动菜单页的意思就是我们在滑动主页面,以显示出或者隐藏菜单页的时候,菜单页相应的也会有一定的移动效果,就好像是主页面带着菜单页面一起滑动一样,

这样在用户体验上比静止不动的菜单页面要更好。除此之外我们额外添加手指快速略过检测速率以及在菜单打开状态下点击主页面关闭菜单的功能。

可能用文字来表述不是很形象,具体的一下效果可以看下面的GIF:


因为我是在虚拟机里面跑的应用然后屏幕截图,总是一卡一卡的,大家先将就看吧,至于点击关闭菜单页以及快速略过打开和关闭菜单页的效果,大家可以在我最下面提供的源码下载里下载代码,然后跑到自己的手机上进行试验。

下面来说我们的实现思路。

我们要实现的这个效果,总体上包含了两部分,菜单页面部分和主页面(美女)部分。我们今天的实现思路是,在初次绘制的过程中,先布局菜单页面,让菜单页面的左上角坐标相对于SlidingMenu坐标系的原点向左侧偏移一定的距离。然后布局主页面,主页面就是整体填充我们的自定义SlidingMenu。

具体的代码如下:

[java] view plain copy
  1. @Override
  2. protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
  3. //设置容器大小,直接填充窗口
  4. intwidth=MeasureSpec.getSize(widthMeasureSpec);
  5. intheight=MeasureSpec.getSize(heightMeasureSpec);
  6. setMeasuredDimension(width,height);
  7. //内部包含两个页面一个menuPage,一个mainPage
  8. //先来设置menuPage的大小,设置为父容器的1/3宽,高度与父容器相同
  9. intmenuPageWidthMeasureSpec=MeasureSpec.makeMeasureSpec(getMeasuredWidth()/3,MeasureSpec.EXACTLY);
  10. intmenuPageHeightMeasureSpec=MeasureSpec.makeMeasureSpec(getMeasuredHeight(),MeasureSpec.EXACTLY);
  11. this.menuPage.measure(menuPageWidthMeasureSpec,menuPageHeightMeasureSpec);
  12. //设置mainPage的大小,填充父容器的大小
  13. intmainPageWidthMeasureSpec=MeasureSpec.makeMeasureSpec(getMeasuredWidth(),MeasureSpec.EXACTLY);
  14. intmainPageHeightMeasureSpec=MeasureSpec.makeMeasureSpec(getMeasuredHeight(),MeasureSpec.EXACTLY);
  15. this.mainPage.measure(mainPageWidthMeasureSpec,mainPageHeightMeasureSpec);
  16. }
  17. @Override
  18. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  19. if(!hasMeasured){
  20. //初始化菜单页面左上角坐标,用来第一次布局菜单页面
  21. menuPageLeft=-getMeasuredWidth()/12;
  22. hasMeasured=true;
  23. }
  24. //校正
  25. if(menuPageLeft>0){
  26. menuPageLeft=0;
  27. }
  28. if(menuPageLeft<-getMeasuredWidth()/12){
  29. menuPageLeft=-getMeasuredWidth()/12;
  30. }
  31. if(mainPageLeft>getMeasuredWidth()/3){
  32. mainPageLeft=getMeasuredWidth()/3;
  33. }
  34. if(mainPageLeft<0){
  35. mainPageLeft=0;
  36. }
  37. intmenuPageTop=0;
  38. intmainPageTop=0;
  39. this.menuPage.layout((int)menuPageLeft,menuPageTop,(int)(menuPageLeft+getMeasuredWidth()/3),menuPageTop+getMeasuredHeight());
  40. this.mainPage.layout((int)mainPageLeft,mainPageTop,(int)(mainPageLeft+getMeasuredWidth()),mainPageTop+getMeasuredHeight());
  41. }

在上面这部分代码 layout过程中,我们把菜单页面向左侧偏移了 -getMeasuredWidth() / 12 距离 也就是 SLidingMenu宽度的 1/12,我们规定的菜单页宽度是getMeasuredWidth() / 3 这个在measure过程中进行了定义,这两个值之间的比例是 1::4 这个比例在后面也有用到。

布局完成之后,我们就应该处理触摸过程了,我们的思路是在用户触摸的时候,根据坐标变化,动态的改变menuPage和mainPage的左上角坐标,然后请求重新布局来实现两个页面的滑动效果。具体代码片段如下:

[java] view plain copy
  1. @Override
  2. publicbooleanonInterceptTouchEvent(MotionEventev){
  3. floatx=ev.getX();
  4. if(x<mainPageLeft){
  5. returnfalse;
  6. }
  7. intaction=ev.getAction();
  8. if(action==MotionEvent.ACTION_MOVE&&mTouchState!=TOUCH_STATE_REST){
  9. returntrue;
  10. }
  11. switch(action){
  12. caseMotionEvent.ACTION_MOVE:
  13. intxDiff=(int)Math.abs(mLastMotionX-x);
  14. if(xDiff>mTouchSlop){
  15. //拦截
  16. mTouchState=TOUCH_STATE_SCROLLING;
  17. }
  18. break;
  19. caseMotionEvent.ACTION_DOWN:
  20. mLastMotionX=x;
  21. break;
  22. caseMotionEvent.ACTION_CANCEL:
  23. caseMotionEvent.ACTION_UP:
  24. mTouchState=TOUCH_STATE_REST;
  25. break;
  26. }
  27. returnmTouchState!=TOUCH_STATE_REST;
  28. }
  29. @Override
  30. publicbooleanonTouchEvent(MotionEventevent){
  31. if(mVelocityTracker==null){
  32. mVelocityTracker=VelocityTracker.obtain();
  33. }
  34. mVelocityTracker.addMovement(event);
  35. floatx=event.getX();
  36. switch(event.getAction()){
  37. caseMotionEvent.ACTION_DOWN:
  38. break;
  39. caseMotionEvent.ACTION_MOVE:
  40. floatdeltaX=mLastMotionX-x;
  41. mainPageLeft-=deltaX;
  42. menuPageLeft-=deltaX/4;
  43. requestLayout();
  44. mLastMotionX=x;
  45. break;
  46. caseMotionEvent.ACTION_UP:
  47. mVelocityTracker.computeCurrentVelocity(1000);
  48. intvelocityX=(int)mVelocityTracker.getXVelocity();
  49. if(velocityX>SNAP_VELOCITY){
  50. //显示菜单页面
  51. openMenu();
  52. }elseif(velocityX<-SNAP_VELOCITY){
  53. //关闭菜单页面
  54. closeMenu();
  55. }else{
  56. snapToDestination();
  57. }
  58. if(mVelocityTracker!=null){
  59. mVelocityTracker.recycle();
  60. mVelocityTracker=null;
  61. }
  62. mTouchState=TOUCH_STATE_REST;
  63. break;
  64. caseMotionEvent.ACTION_CANCEL:
  65. mTouchState=TOUCH_STATE_REST;
  66. break;
  67. }
  68. returntrue;
  69. }

在onTouchEvent中,我们在处理Move事件的时候,有这样两行代码,mainPageLeft -= deltaX;menuPageLeft -= deltaX / 4; 这里用到了我们前面用到的1:4 的比例关系。

openMenu()和closeMenu()代码片段如下:这一段需要解释的地方不多:

[java] view plain copy
  1. /**
  2. *快速略过,显示菜单页面
  3. */
  4. publicvoidopenMenu(){
  5. //主界面
  6. newThread(newRunnable(){
  7. @Override
  8. publicvoidrun(){
  9. while(mainPageLeft<=getMeasuredWidth()/3){
  10. mainPageLeft+=10;
  11. try{
  12. Thread.sleep(10);
  13. }catch(InterruptedExceptione){
  14. e.printStackTrace();
  15. }
  16. uiHandler.obtainMessage(0x00,0x01,0x00).sendToTarget();
  17. }
  18. }
  19. }).start();
  20. //菜单界面
  21. newThread(newRunnable(){
  22. @Override
  23. publicvoidrun(){
  24. while(menuPageLeft<=0){
  25. menuPageLeft+=2.5;
  26. try{
  27. Thread.sleep(10);
  28. }catch(InterruptedExceptione){
  29. e.printStackTrace();
  30. }
  31. uiHandler.obtainMessage(0x00,0x01,0x00).sendToTarget();
  32. }
  33. }
  34. }).start();
  35. }
  36. /**
  37. *快速略过,关闭菜单页面
  38. */
  39. publicvoidcloseMenu(){
  40. //主界面
  41. newThread(newRunnable(){
  42. @Override
  43. publicvoidrun(){
  44. while(mainPageLeft>=0){
  45. mainPageLeft-=10;
  46. try{
  47. Thread.sleep(10);
  48. }catch(InterruptedExceptione){
  49. e.printStackTrace();
  50. }
  51. uiHandler.obtainMessage(0x00,0x01,0x00).sendToTarget();
  52. }
  53. }
  54. }).start();
  55. //菜单页面
  56. newThread(newRunnable(){
  57. @Override
  58. publicvoidrun(){
  59. while(menuPageLeft>=-getMeasuredWidth()/12){
  60. menuPageLeft-=2.5;
  61. try{
  62. Thread.sleep(10);
  63. }catch(InterruptedExceptione){
  64. e.printStackTrace();
  65. }
  66. uiHandler.obtainMessage(0x00,0x01,0x00).sendToTarget();
  67. }
  68. }
  69. }).start();
  70. }
上面代码中 10 与 2.5 这两个值也是用到了前面 1:4的比例关系。


好了,相信大家仔细读完上面的文字都会明白我实现的思路了,下面是完整的代码和源码下载,我做的注释还是比较全的,仔细看一定能明白原理!

[java] view plain copy
  1. packagecom.example.demo;
  2. importjava.lang.ref.WeakReference;
  3. importandroid.content.Context;
  4. importandroid.os.Handler;
  5. importandroid.os.Message;
  6. importandroid.util.AttributeSet;
  7. importandroid.view.MotionEvent;
  8. importandroid.view.VelocityTracker;
  9. importandroid.view.View;
  10. importandroid.view.ViewConfiguration;
  11. importandroid.view.ViewGroup;
  12. importandroid.widget.FrameLayout;
  13. importandroid.view.View.OnClickListener;
  14. /**
  15. *滑动菜单界面容器
  16. *@authorcarrey
  17. *
  18. */
  19. publicclassSlidingMenuViewGroupextendsViewGroupimplementsOnClickListener{
  20. /**场景*/
  21. privateContextmContext;
  22. /**菜单页面*/
  23. privateViewGroupmenuPage;
  24. /**主页面*/
  25. privateFrameLayoutmainPage;
  26. /**静止状态值*/
  27. privatefinalintTOUCH_STATE_REST=0;
  28. /**滚动状态值*/
  29. privatefinalintTOUCH_STATE_SCROLLING=1;
  30. /**当前的滚动与否状态*/
  31. privateintmTouchState=TOUCH_STATE_REST;
  32. /**能够拦截左右划屏事件的最小判断距离*/
  33. privateintmTouchSlop;
  34. /**记录上一次触摸事件的x坐标*/
  35. privatefloatmLastMotionX;
  36. /**检测快速略过屏幕动作的速率*/
  37. privateVelocityTrackermVelocityTracker;
  38. /**快速略过动作有效的最小速度*/
  39. privatefinalintSNAP_VELOCITY=600;
  40. /**主页面左上角坐标*/
  41. privatefloatmainPageLeft;
  42. /**菜单页面左上角坐标*/
  43. privatefloatmenuPageLeft;
  44. /**是否经历过measure过程的标记*/
  45. privatebooleanhasMeasured;
  46. /**页面Handler*/
  47. privateUIHandleruiHandler;
  48. publicSlidingMenuViewGroup(Contextcontext){
  49. super(context);
  50. this.mContext=context;
  51. init();
  52. }
  53. publicSlidingMenuViewGroup(Contextcontext,AttributeSetattrs){
  54. super(context,attrs);
  55. this.mContext=context;
  56. init();
  57. }
  58. /**
  59. *一些参数的初始化操作
  60. */
  61. privatevoidinit(){
  62. mTouchSlop=ViewConfiguration.get(mContext).getScaledEdgeSlop();
  63. uiHandler=newUIHandler(this);
  64. }
  65. /**
  66. *把菜单页面和主页面添加安装进容器内
  67. *@parammenuPage
  68. *@parammainPage
  69. */
  70. publicvoidsetupMenuAndMainPage(ViewGroupmenuPage,FrameLayoutmainPage){
  71. this.menuPage=menuPage;
  72. this.mainPage=mainPage;
  73. this.mainPage.setOnClickListener(this);
  74. this.addView(this.menuPage);
  75. this.addView(this.mainPage);
  76. }
  77. /**
  78. *点击主界面,在打开的情况下,会关闭菜单页面
  79. */
  80. @Override
  81. publicvoidonClick(Viewv){
  82. if(mainPageLeft==getMeasuredWidth()/3){
  83. closeMenu();
  84. }
  85. }
  86. @Override
  87. protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
  88. //设置容器大小,直接填充窗口
  89. intwidth=MeasureSpec.getSize(widthMeasureSpec);
  90. intheight=MeasureSpec.getSize(heightMeasureSpec);
  91. setMeasuredDimension(width,height);
  92. //内部包含两个页面一个menuPage,一个mainPage
  93. //先来设置menuPage的大小,设置为父容器的1/3宽,高度与父容器相同
  94. intmenuPageWidthMeasureSpec=MeasureSpec.makeMeasureSpec(getMeasuredWidth()/3,MeasureSpec.EXACTLY);
  95. intmenuPageHeightMeasureSpec=MeasureSpec.makeMeasureSpec(getMeasuredHeight(),MeasureSpec.EXACTLY);
  96. this.menuPage.measure(menuPageWidthMeasureSpec,menuPageHeightMeasureSpec);
  97. //设置mainPage的大小,填充父容器的大小
  98. intmainPageWidthMeasureSpec=MeasureSpec.makeMeasureSpec(getMeasuredWidth(),MeasureSpec.EXACTLY);
  99. intmainPageHeightMeasureSpec=MeasureSpec.makeMeasureSpec(getMeasuredHeight(),MeasureSpec.EXACTLY);
  100. this.mainPage.measure(mainPageWidthMeasureSpec,mainPageHeightMeasureSpec);
  101. }
  102. @Override
  103. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  104. if(!hasMeasured){
  105. //初始化菜单页面左上角坐标,用来第一次布局菜单页面
  106. menuPageLeft=-getMeasuredWidth()/12;
  107. hasMeasured=true;
  108. }
  109. //校正
  110. if(menuPageLeft>0){
  111. menuPageLeft=0;
  112. }
  113. if(menuPageLeft<-getMeasuredWidth()/12){
  114. menuPageLeft=-getMeasuredWidth()/12;
  115. }
  116. if(mainPageLeft>getMeasuredWidth()/3){
  117. mainPageLeft=getMeasuredWidth()/3;
  118. }
  119. if(mainPageLeft<0){
  120. mainPageLeft=0;
  121. }
  122. intmenuPageTop=0;
  123. intmainPageTop=0;
  124. this.menuPage.layout((int)menuPageLeft,menuPageTop,(int)(menuPageLeft+getMeasuredWidth()/3),menuPageTop+getMeasuredHeight());
  125. this.mainPage.layout((int)mainPageLeft,mainPageTop,(int)(mainPageLeft+getMeasuredWidth()),mainPageTop+getMeasuredHeight());
  126. }
  127. @Override
  128. publicbooleanonInterceptTouchEvent(MotionEventev){
  129. floatx=ev.getX();
  130. if(x<mainPageLeft){
  131. returnfalse;
  132. }
  133. intaction=ev.getAction();
  134. if(action==MotionEvent.ACTION_MOVE&&mTouchState!=TOUCH_STATE_REST){
  135. returntrue;
  136. }
  137. switch(action){
  138. caseMotionEvent.ACTION_MOVE:
  139. intxDiff=(int)Math.abs(mLastMotionX-x);
  140. if(xDiff>mTouchSlop){
  141. //拦截
  142. mTouchState=TOUCH_STATE_SCROLLING;
  143. }
  144. break;
  145. caseMotionEvent.ACTION_DOWN:
  146. mLastMotionX=x;
  147. break;
  148. caseMotionEvent.ACTION_CANCEL:
  149. caseMotionEvent.ACTION_UP:
  150. mTouchState=TOUCH_STATE_REST;
  151. break;
  152. }
  153. returnmTouchState!=TOUCH_STATE_REST;
  154. }
  155. @Override
  156. publicbooleanonTouchEvent(MotionEventevent){
  157. if(mVelocityTracker==null){
  158. mVelocityTracker=VelocityTracker.obtain();
  159. }
  160. mVelocityTracker.addMovement(event);
  161. floatx=event.getX();
  162. switch(event.getAction()){
  163. caseMotionEvent.ACTION_DOWN:
  164. break;
  165. caseMotionEvent.ACTION_MOVE:
  166. floatdeltaX=mLastMotionX-x;
  167. mainPageLeft-=deltaX;
  168. menuPageLeft-=deltaX/4;
  169. requestLayout();
  170. mLastMotionX=x;
  171. break;
  172. caseMotionEvent.ACTION_UP:
  173. mVelocityTracker.computeCurrentVelocity(1000);
  174. intvelocityX=(int)mVelocityTracker.getXVelocity();
  175. if(velocityX>SNAP_VELOCITY){
  176. //显示菜单页面
  177. openMenu();
  178. }elseif(velocityX<-SNAP_VELOCITY){
  179. //关闭菜单页面
  180. closeMenu();
  181. }else{
  182. snapToDestination();
  183. }
  184. if(mVelocityTracker!=null){
  185. mVelocityTracker.recycle();
  186. mVelocityTracker=null;
  187. }
  188. mTouchState=TOUCH_STATE_REST;
  189. break;
  190. caseMotionEvent.ACTION_CANCEL:
  191. mTouchState=TOUCH_STATE_REST;
  192. break;
  193. }
  194. returntrue;
  195. }
  196. /**
  197. *UIHandler
  198. *@authorcarrey
  199. *
  200. */
  201. privatestaticclassUIHandlerextendsHandler{
  202. privateViewGroupcontainer;
  203. privateWeakReference<ViewGroup>containerWR;
  204. publicUIHandler(ViewGroupcontainer){
  205. this.containerWR=newWeakReference<ViewGroup>(container);
  206. this.container=containerWR.get();
  207. }
  208. @Override
  209. publicvoidhandleMessage(Messagemsg){
  210. switch(msg.arg1){
  211. case0x01:
  212. container.requestLayout();
  213. break;
  214. }
  215. super.handleMessage(msg);
  216. }
  217. }
  218. /**
  219. *手指缓缓滑动后离开屏幕的处理
  220. */
  221. privatevoidsnapToDestination(){
  222. //通过mainPageLeft来判断滚动到的页面
  223. if(mainPageLeft>=getMeasuredWidth()/6){
  224. openMenu();
  225. }else{
  226. closeMenu();
  227. }
  228. }
  229. /**
  230. *快速略过,显示菜单页面
  231. */
  232. publicvoidopenMenu(){
  233. //主界面
  234. newThread(newRunnable(){
  235. @Override
  236. publicvoidrun(){
  237. while(mainPageLeft<=getMeasuredWidth()/3){
  238. mainPageLeft+=10;
  239. try{
  240. Thread.sleep(10);
  241. }catch(InterruptedExceptione){
  242. e.printStackTrace();
  243. }
  244. uiHandler.obtainMessage(0x00,0x01,0x00).sendToTarget();
  245. }
  246. }
  247. }).start();
  248. //菜单界面
  249. newThread(newRunnable(){
  250. @Override
  251. publicvoidrun(){
  252. while(menuPageLeft<=0){
  253. menuPageLeft+=2.5;
  254. try{
  255. Thread.sleep(10);
  256. }catch(InterruptedExceptione){
  257. e.printStackTrace();
  258. }
  259. uiHandler.obtainMessage(0x00,0x01,0x00).sendToTarget();
  260. }
  261. }
  262. }).start();
  263. }
  264. /**
  265. *快速略过,关闭菜单页面
  266. */
  267. publicvoidcloseMenu(){
  268. //主界面
  269. newThread(newRunnable(){
  270. @Override
  271. publicvoidrun(){
  272. while(mainPageLeft>=0){
  273. mainPageLeft-=10;
  274. try{
  275. Thread.sleep(10);
  276. }catch(InterruptedExceptione){
  277. e.printStackTrace();
  278. }
  279. uiHandler.obtainMessage(0x00,0x01,0x00).sendToTarget();
  280. }
  281. }
  282. }).start();
  283. //菜单页面
  284. newThread(newRunnable(){
  285. @Override
  286. publicvoidrun(){
  287. while(menuPageLeft>=-getMeasuredWidth()/12){
  288. menuPageLeft-=2.5;
  289. try{
  290. Thread.sleep(10);
  291. }catch(InterruptedExceptione){
  292. e.printStackTrace();
  293. }
  294. uiHandler.obtainMessage(0x00,0x01,0x00).sendToTarget();
  295. }
  296. }
  297. }).start();
  298. }
  299. }

源码下载


更多相关文章

  1. wp7开发第一课:软件生命周期(其一)
  2. Android(安卓)UI 之 我的页面 圆形图片+通用item封装(简化代码量)
  3. android 模拟器 自定义分辨率 没有键盘
  4. Android(安卓)CoordinatorLayout 沉浸式状态栏
  5. android 应用内悬浮框,并在指定页面显示
  6. android – 页面初始化时让组件得不到焦点
  7. android 动态菜单组件
  8. h5页面打开app,安卓端和苹果端
  9. Android使用ViewFlipper做页面切换,与手势滑动切换的使用

随机推荐

  1. Android中的 Thread + Handler 线程简单
  2. android monkey app乱点测试
  3. 右侧滑出式提示框改良
  4. Android(安卓)存储:SD卡剩余空间的检测
  5. Android(安卓)XML文件中引用资源的方法
  6. Android:MediaRecoder——录制音视频
  7. 编译FFmpeg4.1.3并移植到Android(安卓)ap
  8. 【分享】Android二次打包植入广告
  9. Android(安卓)数据绑定 (Data Binding)
  10. Android多点触控实现图片自由缩放