`
20386053
  • 浏览: 448827 次
文章分类
社区版块
存档分类
最新评论

Android双向滑动菜单完全解析,教你如何一分钟实现双向滑动特效

 
阅读更多

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9671609

记得在很早之前,我写了一篇关于Android滑动菜单的文章,其中有一个朋友在评论中留言,希望我可以帮他将这个滑动菜单改成双向滑动的方式。当时也没想花太多时间,简单修改了一下就发给了他,结果没想到后来却有一大批的朋友都来问我要这份双向滑动菜单的代码。由于这份代码写得很不用心,我发了部分朋友之后实在不忍心继续发下去了,于是决定专门写一篇文章来介绍更好的Android双向滑动菜单的实现方法。

在开始动手之前先来讲一下实现原理,在一个Activity的布局中需要有三部分,一个是左侧菜单的布局,一个是右侧菜单的布局,一个是内容布局。左侧菜单居屏幕左边缘对齐,右侧菜单居屏幕右边缘对齐,然后内容布局占满整个屏幕,并压在了左侧菜单和右侧菜单的上面。当用户手指向右滑动时,将右侧菜单隐藏,左侧菜单显示,然后通过偏移内容布局的位置,就可以让左侧菜单展现出来。同样的道理,当用户手指向左滑动时,将左侧菜单隐藏,右侧菜单显示,也是通过偏移内容布局的位置,就可以让右侧菜单展现出来。原理示意图所下所示:


介绍完了原理,我们就开始动手实现吧。新建一个Android项目,项目名就叫做BidirSlidingLayout。然后新建我们最主要的BidirSlidingLayout类,这个类就是实现双向滑动菜单功能的核心类,代码如下所示:

  1. publicclassBidirSlidingLayoutextendsRelativeLayoutimplementsOnTouchListener{
  2. /**
  3. *滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。
  4. */
  5. publicstaticfinalintSNAP_VELOCITY=200;
  6. /**
  7. *滑动状态的一种,表示未进行任何滑动。
  8. */
  9. publicstaticfinalintDO_NOTHING=0;
  10. /**
  11. *滑动状态的一种,表示正在滑出左侧菜单。
  12. */
  13. publicstaticfinalintSHOW_LEFT_MENU=1;
  14. /**
  15. *滑动状态的一种,表示正在滑出右侧菜单。
  16. */
  17. publicstaticfinalintSHOW_RIGHT_MENU=2;
  18. /**
  19. *滑动状态的一种,表示正在隐藏左侧菜单。
  20. */
  21. publicstaticfinalintHIDE_LEFT_MENU=3;
  22. /**
  23. *滑动状态的一种,表示正在隐藏右侧菜单。
  24. */
  25. publicstaticfinalintHIDE_RIGHT_MENU=4;
  26. /**
  27. *记录当前的滑动状态
  28. */
  29. privateintslideState;
  30. /**
  31. *屏幕宽度值。
  32. */
  33. privateintscreenWidth;
  34. /**
  35. *在被判定为滚动之前用户手指可以移动的最大值。
  36. */
  37. privateinttouchSlop;
  38. /**
  39. *记录手指按下时的横坐标。
  40. */
  41. privatefloatxDown;
  42. /**
  43. *记录手指按下时的纵坐标。
  44. */
  45. privatefloatyDown;
  46. /**
  47. *记录手指移动时的横坐标。
  48. */
  49. privatefloatxMove;
  50. /**
  51. *记录手指移动时的纵坐标。
  52. */
  53. privatefloatyMove;
  54. /**
  55. *记录手机抬起时的横坐标。
  56. */
  57. privatefloatxUp;
  58. /**
  59. *左侧菜单当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
  60. */
  61. privatebooleanisLeftMenuVisible;
  62. /**
  63. *右侧菜单当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
  64. */
  65. privatebooleanisRightMenuVisible;
  66. /**
  67. *是否正在滑动。
  68. */
  69. privatebooleanisSliding;
  70. /**
  71. *左侧菜单布局对象。
  72. */
  73. privateViewleftMenuLayout;
  74. /**
  75. *右侧菜单布局对象。
  76. */
  77. privateViewrightMenuLayout;
  78. /**
  79. *内容布局对象。
  80. */
  81. privateViewcontentLayout;
  82. /**
  83. *用于监听滑动事件的View。
  84. */
  85. privateViewmBindView;
  86. /**
  87. *左侧菜单布局的参数。
  88. */
  89. privateMarginLayoutParamsleftMenuLayoutParams;
  90. /**
  91. *右侧菜单布局的参数。
  92. */
  93. privateMarginLayoutParamsrightMenuLayoutParams;
  94. /**
  95. *内容布局的参数。
  96. */
  97. privateRelativeLayout.LayoutParamscontentLayoutParams;
  98. /**
  99. *用于计算手指滑动的速度。
  100. */
  101. privateVelocityTrackermVelocityTracker;
  102. /**
  103. *重写BidirSlidingLayout的构造函数,其中获取了屏幕的宽度和touchSlop的值。
  104. *
  105. *@paramcontext
  106. *@paramattrs
  107. */
  108. publicBidirSlidingLayout(Contextcontext,AttributeSetattrs){
  109. super(context,attrs);
  110. WindowManagerwm=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
  111. screenWidth=wm.getDefaultDisplay().getWidth();
  112. touchSlop=ViewConfiguration.get(context).getScaledTouchSlop();
  113. }
  114. /**
  115. *绑定监听滑动事件的View。
  116. *
  117. *@parambindView
  118. *需要绑定的View对象。
  119. */
  120. publicvoidsetScrollEvent(ViewbindView){
  121. mBindView=bindView;
  122. mBindView.setOnTouchListener(this);
  123. }
  124. /**
  125. *将界面滚动到左侧菜单界面,滚动速度设定为-30.
  126. */
  127. publicvoidscrollToLeftMenu(){
  128. newLeftMenuScrollTask().execute(-30);
  129. }
  130. /**
  131. *将界面滚动到右侧菜单界面,滚动速度设定为-30.
  132. */
  133. publicvoidscrollToRightMenu(){
  134. newRightMenuScrollTask().execute(-30);
  135. }
  136. /**
  137. *将界面从左侧菜单滚动到内容界面,滚动速度设定为30.
  138. */
  139. publicvoidscrollToContentFromLeftMenu(){
  140. newLeftMenuScrollTask().execute(30);
  141. }
  142. /**
  143. *将界面从右侧菜单滚动到内容界面,滚动速度设定为30.
  144. */
  145. publicvoidscrollToContentFromRightMenu(){
  146. newRightMenuScrollTask().execute(30);
  147. }
  148. /**
  149. *左侧菜单是否完全显示出来,滑动过程中此值无效。
  150. *
  151. *@return左侧菜单完全显示返回true,否则返回false。
  152. */
  153. publicbooleanisLeftLayoutVisible(){
  154. returnisLeftMenuVisible;
  155. }
  156. /**
  157. *右侧菜单是否完全显示出来,滑动过程中此值无效。
  158. *
  159. *@return右侧菜单完全显示返回true,否则返回false。
  160. */
  161. publicbooleanisRightLayoutVisible(){
  162. returnisRightMenuVisible;
  163. }
  164. /**
  165. *在onLayout中重新设定左侧菜单、右侧菜单、以及内容布局的参数。
  166. */
  167. @Override
  168. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  169. super.onLayout(changed,l,t,r,b);
  170. if(changed){
  171. //获取左侧菜单布局对象
  172. leftMenuLayout=getChildAt(0);
  173. leftMenuLayoutParams=(MarginLayoutParams)leftMenuLayout.getLayoutParams();
  174. //获取右侧菜单布局对象
  175. rightMenuLayout=getChildAt(1);
  176. rightMenuLayoutParams=(MarginLayoutParams)rightMenuLayout.getLayoutParams();
  177. //获取内容布局对象
  178. contentLayout=getChildAt(2);
  179. contentLayoutParams=(RelativeLayout.LayoutParams)contentLayout.getLayoutParams();
  180. contentLayoutParams.width=screenWidth;
  181. contentLayout.setLayoutParams(contentLayoutParams);
  182. }
  183. }
  184. @Override
  185. publicbooleanonTouch(Viewv,MotionEventevent){
  186. createVelocityTracker(event);
  187. switch(event.getAction()){
  188. caseMotionEvent.ACTION_DOWN:
  189. //手指按下时,记录按下时的坐标
  190. xDown=event.getRawX();
  191. yDown=event.getRawY();
  192. //将滑动状态初始化为DO_NOTHING
  193. slideState=DO_NOTHING;
  194. break;
  195. caseMotionEvent.ACTION_MOVE:
  196. xMove=event.getRawX();
  197. yMove=event.getRawY();
  198. //手指移动时,对比按下时的坐标,计算出移动的距离。
  199. intmoveDistanceX=(int)(xMove-xDown);
  200. intmoveDistanceY=(int)(yMove-yDown);
  201. //检查当前的滑动状态
  202. checkSlideState(moveDistanceX,moveDistanceY);
  203. //根据当前滑动状态决定如何偏移内容布局
  204. switch(slideState){
  205. caseSHOW_LEFT_MENU:
  206. contentLayoutParams.rightMargin=-moveDistanceX;
  207. checkLeftMenuBorder();
  208. contentLayout.setLayoutParams(contentLayoutParams);
  209. break;
  210. caseHIDE_LEFT_MENU:
  211. contentLayoutParams.rightMargin=-leftMenuLayoutParams.width-moveDistanceX;
  212. checkLeftMenuBorder();
  213. contentLayout.setLayoutParams(contentLayoutParams);
  214. caseSHOW_RIGHT_MENU:
  215. contentLayoutParams.leftMargin=moveDistanceX;
  216. checkRightMenuBorder();
  217. contentLayout.setLayoutParams(contentLayoutParams);
  218. break;
  219. caseHIDE_RIGHT_MENU:
  220. contentLayoutParams.leftMargin=-rightMenuLayoutParams.width+moveDistanceX;
  221. checkRightMenuBorder();
  222. contentLayout.setLayoutParams(contentLayoutParams);
  223. default:
  224. break;
  225. }
  226. break;
  227. caseMotionEvent.ACTION_UP:
  228. xUp=event.getRawX();
  229. intupDistanceX=(int)(xUp-xDown);
  230. if(isSliding){
  231. //手指抬起时,进行判断当前手势的意图
  232. switch(slideState){
  233. caseSHOW_LEFT_MENU:
  234. if(shouldScrollToLeftMenu()){
  235. scrollToLeftMenu();
  236. }else{
  237. scrollToContentFromLeftMenu();
  238. }
  239. break;
  240. caseHIDE_LEFT_MENU:
  241. if(shouldScrollToContentFromLeftMenu()){
  242. scrollToContentFromLeftMenu();
  243. }else{
  244. scrollToLeftMenu();
  245. }
  246. break;
  247. caseSHOW_RIGHT_MENU:
  248. if(shouldScrollToRightMenu()){
  249. scrollToRightMenu();
  250. }else{
  251. scrollToContentFromRightMenu();
  252. }
  253. break;
  254. caseHIDE_RIGHT_MENU:
  255. if(shouldScrollToContentFromRightMenu()){
  256. scrollToContentFromRightMenu();
  257. }else{
  258. scrollToRightMenu();
  259. }
  260. break;
  261. default:
  262. break;
  263. }
  264. }elseif(upDistanceX<touchSlop&&isLeftMenuVisible){
  265. //当左侧菜单显示时,如果用户点击一下内容部分,则直接滚动到内容界面
  266. scrollToContentFromLeftMenu();
  267. }elseif(upDistanceX<touchSlop&&isRightMenuVisible){
  268. //当右侧菜单显示时,如果用户点击一下内容部分,则直接滚动到内容界面
  269. scrollToContentFromRightMenu();
  270. }
  271. recycleVelocityTracker();
  272. break;
  273. }
  274. if(v.isEnabled()){
  275. if(isSliding){
  276. //正在滑动时让控件得不到焦点
  277. unFocusBindView();
  278. returntrue;
  279. }
  280. if(isLeftMenuVisible||isRightMenuVisible){
  281. //当左侧或右侧布局显示时,将绑定控件的事件屏蔽掉
  282. returntrue;
  283. }
  284. returnfalse;
  285. }
  286. returntrue;
  287. }
  288. /**
  289. *根据手指移动的距离,判断当前用户的滑动意图,然后给slideState赋值成相应的滑动状态值。
  290. *
  291. *@parammoveDistanceX
  292. *横向移动的距离
  293. *@parammoveDistanceY
  294. *纵向移动的距离
  295. */
  296. privatevoidcheckSlideState(intmoveDistanceX,intmoveDistanceY){
  297. if(isLeftMenuVisible){
  298. if(!isSliding&&Math.abs(moveDistanceX)>=touchSlop&&moveDistanceX<0){
  299. isSliding=true;
  300. slideState=HIDE_LEFT_MENU;
  301. }
  302. }elseif(isRightMenuVisible){
  303. if(!isSliding&&Math.abs(moveDistanceX)>=touchSlop&&moveDistanceX>0){
  304. isSliding=true;
  305. slideState=HIDE_RIGHT_MENU;
  306. }
  307. }else{
  308. if(!isSliding&&Math.abs(moveDistanceX)>=touchSlop&&moveDistanceX>0
  309. &&Math.abs(moveDistanceY)<touchSlop){
  310. isSliding=true;
  311. slideState=SHOW_LEFT_MENU;
  312. contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,0);
  313. contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
  314. contentLayout.setLayoutParams(contentLayoutParams);
  315. //如果用户想要滑动左侧菜单,将左侧菜单显示,右侧菜单隐藏
  316. leftMenuLayout.setVisibility(View.VISIBLE);
  317. rightMenuLayout.setVisibility(View.GONE);
  318. }elseif(!isSliding&&Math.abs(moveDistanceX)>=touchSlop&&moveDistanceX<0
  319. &&Math.abs(moveDistanceY)<touchSlop){
  320. isSliding=true;
  321. slideState=SHOW_RIGHT_MENU;
  322. contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,0);
  323. contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
  324. contentLayout.setLayoutParams(contentLayoutParams);
  325. //如果用户想要滑动右侧菜单,将右侧菜单显示,左侧菜单隐藏
  326. rightMenuLayout.setVisibility(View.VISIBLE);
  327. leftMenuLayout.setVisibility(View.GONE);
  328. }
  329. }
  330. }
  331. /**
  332. *在滑动过程中检查左侧菜单的边界值,防止绑定布局滑出屏幕。
  333. */
  334. privatevoidcheckLeftMenuBorder(){
  335. if(contentLayoutParams.rightMargin>0){
  336. contentLayoutParams.rightMargin=0;
  337. }elseif(contentLayoutParams.rightMargin<-leftMenuLayoutParams.width){
  338. contentLayoutParams.rightMargin=-leftMenuLayoutParams.width;
  339. }
  340. }
  341. /**
  342. *在滑动过程中检查右侧菜单的边界值,防止绑定布局滑出屏幕。
  343. */
  344. privatevoidcheckRightMenuBorder(){
  345. if(contentLayoutParams.leftMargin>0){
  346. contentLayoutParams.leftMargin=0;
  347. }elseif(contentLayoutParams.leftMargin<-rightMenuLayoutParams.width){
  348. contentLayoutParams.leftMargin=-rightMenuLayoutParams.width;
  349. }
  350. }
  351. /**
  352. *判断是否应该滚动将左侧菜单展示出来。如果手指移动距离大于左侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
  353. *就认为应该滚动将左侧菜单展示出来。
  354. *
  355. *@return如果应该将左侧菜单展示出来返回true,否则返回false。
  356. */
  357. privatebooleanshouldScrollToLeftMenu(){
  358. returnxUp-xDown>leftMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY;
  359. }
  360. /**
  361. *判断是否应该滚动将右侧菜单展示出来。如果手指移动距离大于右侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
  362. *就认为应该滚动将右侧菜单展示出来。
  363. *
  364. *@return如果应该将右侧菜单展示出来返回true,否则返回false。
  365. */
  366. privatebooleanshouldScrollToRightMenu(){
  367. returnxDown-xUp>rightMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY;
  368. }
  369. /**
  370. *判断是否应该从左侧菜单滚动到内容布局,如果手指移动距离大于左侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
  371. *就认为应该从左侧菜单滚动到内容布局。
  372. *
  373. *@return如果应该从左侧菜单滚动到内容布局返回true,否则返回false。
  374. */
  375. privatebooleanshouldScrollToContentFromLeftMenu(){
  376. returnxDown-xUp>leftMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY;
  377. }
  378. /**
  379. *判断是否应该从右侧菜单滚动到内容布局,如果手指移动距离大于右侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
  380. *就认为应该从右侧菜单滚动到内容布局。
  381. *
  382. *@return如果应该从右侧菜单滚动到内容布局返回true,否则返回false。
  383. */
  384. privatebooleanshouldScrollToContentFromRightMenu(){
  385. returnxUp-xDown>rightMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY;
  386. }
  387. /**
  388. *创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。
  389. *
  390. *@paramevent
  391. *右侧布局监听控件的滑动事件
  392. */
  393. privatevoidcreateVelocityTracker(MotionEventevent){
  394. if(mVelocityTracker==null){
  395. mVelocityTracker=VelocityTracker.obtain();
  396. }
  397. mVelocityTracker.addMovement(event);
  398. }
  399. /**
  400. *获取手指在绑定布局上的滑动速度。
  401. *
  402. *@return滑动速度,以每秒钟移动了多少像素值为单位。
  403. */
  404. privateintgetScrollVelocity(){
  405. mVelocityTracker.computeCurrentVelocity(1000);
  406. intvelocity=(int)mVelocityTracker.getXVelocity();
  407. returnMath.abs(velocity);
  408. }
  409. /**
  410. *回收VelocityTracker对象。
  411. */
  412. privatevoidrecycleVelocityTracker(){
  413. mVelocityTracker.recycle();
  414. mVelocityTracker=null;
  415. }
  416. /**
  417. *使用可以获得焦点的控件在滑动的时候失去焦点。
  418. */
  419. privatevoidunFocusBindView(){
  420. if(mBindView!=null){
  421. mBindView.setPressed(false);
  422. mBindView.setFocusable(false);
  423. mBindView.setFocusableInTouchMode(false);
  424. }
  425. }
  426. classLeftMenuScrollTaskextendsAsyncTask<Integer,Integer,Integer>{
  427. @Override
  428. protectedIntegerdoInBackground(Integer...speed){
  429. intrightMargin=contentLayoutParams.rightMargin;
  430. //根据传入的速度来滚动界面,当滚动到达边界值时,跳出循环。
  431. while(true){
  432. rightMargin=rightMargin+speed[0];
  433. if(rightMargin<-leftMenuLayoutParams.width){
  434. rightMargin=-leftMenuLayoutParams.width;
  435. break;
  436. }
  437. if(rightMargin>0){
  438. rightMargin=0;
  439. break;
  440. }
  441. publishProgress(rightMargin);
  442. //为了要有滚动效果产生,每次循环使线程睡眠一段时间,这样肉眼才能够看到滚动动画。
  443. sleep(15);
  444. }
  445. if(speed[0]>0){
  446. isLeftMenuVisible=false;
  447. }else{
  448. isLeftMenuVisible=true;
  449. }
  450. isSliding=false;
  451. returnrightMargin;
  452. }
  453. @Override
  454. protectedvoidonProgressUpdate(Integer...rightMargin){
  455. contentLayoutParams.rightMargin=rightMargin[0];
  456. contentLayout.setLayoutParams(contentLayoutParams);
  457. unFocusBindView();
  458. }
  459. @Override
  460. protectedvoidonPostExecute(IntegerrightMargin){
  461. contentLayoutParams.rightMargin=rightMargin;
  462. contentLayout.setLayoutParams(contentLayoutParams);
  463. }
  464. }
  465. classRightMenuScrollTaskextendsAsyncTask<Integer,Integer,Integer>{
  466. @Override
  467. protectedIntegerdoInBackground(Integer...speed){
  468. intleftMargin=contentLayoutParams.leftMargin;
  469. //根据传入的速度来滚动界面,当滚动到达边界值时,跳出循环。
  470. while(true){
  471. leftMargin=leftMargin+speed[0];
  472. if(leftMargin<-rightMenuLayoutParams.width){
  473. leftMargin=-rightMenuLayoutParams.width;
  474. break;
  475. }
  476. if(leftMargin>0){
  477. leftMargin=0;
  478. break;
  479. }
  480. publishProgress(leftMargin);
  481. //为了要有滚动效果产生,每次循环使线程睡眠一段时间,这样肉眼才能够看到滚动动画。
  482. sleep(15);
  483. }
  484. if(speed[0]>0){
  485. isRightMenuVisible=false;
  486. }else{
  487. isRightMenuVisible=true;
  488. }
  489. isSliding=false;
  490. returnleftMargin;
  491. }
  492. @Override
  493. protectedvoidonProgressUpdate(Integer...leftMargin){
  494. contentLayoutParams.leftMargin=leftMargin[0];
  495. contentLayout.setLayoutParams(contentLayoutParams);
  496. unFocusBindView();
  497. }
  498. @Override
  499. protectedvoidonPostExecute(IntegerleftMargin){
  500. contentLayoutParams.leftMargin=leftMargin;
  501. contentLayout.setLayoutParams(contentLayoutParams);
  502. }
  503. }
  504. /**
  505. *使当前线程睡眠指定的毫秒数。
  506. *
  507. *@parammillis
  508. *指定当前线程睡眠多久,以毫秒为单位
  509. */
  510. privatevoidsleep(longmillis){
  511. try{
  512. Thread.sleep(millis);
  513. }catch(InterruptedExceptione){
  514. e.printStackTrace();
  515. }
  516. }
  517. }
以上代码注释已经写得非常详细,我再来简单解释一下。首先在onLayout()方法中分别获取到左侧菜单、右侧菜单和内容布局的参数,并将内容布局的宽度重定义成屏幕的宽度,这样就可以保证内容布局既能覆盖住下面的菜单布局,还能偏移出屏幕。然后在onTouch()方法中监听触屏事件,以判断用户手势的意图。这里事先定义好了几种滑动状态,DO_NOTHING表示没有进行任何滑动,SHOW_LEFT_MENU表示用户想要滑出左侧菜单,SHOW_RIGHT_MENU表示用户想要滑出右侧菜单,HIDE_LEFT_MENU表示用户想要隐藏左侧菜单,HIDE_RIGHT_MENU表示用户想要隐藏右侧菜单,在checkSlideState()方法中判断出用户到底是想进行哪一种滑动操作,并给slideState变量赋值,然后根据slideState的值决定如何偏移内容布局。接着当用户手指离开屏幕时,会根据当前的滑动距离,决定后续的滚动方向,通过LeftMenuScrollTask和RightMenuScrollTask来完成完整的滑动过程。另外在滑动的过程,内容布局上的事件会被屏蔽掉,主要是通过一系列的return操作实现的,对这一部分不理解的朋友,请参阅Android事件分发机制完全解析,带你从源码的角度彻底理解

然后我们看一下setScrollEvent方法,这个方法接收一个View作为参数,然后为这个View绑定了一个touch事件。这是什么意思呢?让我们来想象一个场景,如果内容布局是一个LinearLayout,我可以通过监听LinearLayout上的touch事件来控制它的偏移。但是如果内容布局的LinearLayout里面加入了一个ListView,而这个ListView又充满了整个LinearLayout,这个时候LinearLayout将不可能再被touch到了,这个时候我们就需要将touch事件注册到ListView上。setScrollEvent方法也就是提供了一个注册接口,touch事件将会注册到传入的View上。

接下来打开或新建activity_main.xml文件,加入如下代码:

  1. <com.example.bidirslidinglayout.BidirSlidingLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:id="@+id/bidir_sliding_layout"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <RelativeLayout
  7. android:id="@+id/left_menu"
  8. android:layout_width="270dip"
  9. android:layout_height="fill_parent"
  10. android:layout_alignParentLeft="true"
  11. android:background="#00ccff"
  12. android:visibility="invisible">
  13. <TextView
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:layout_centerInParent="true"
  17. android:text="Thisisleftmenu"
  18. android:textColor="#000000"
  19. android:textSize="28sp"/>
  20. </RelativeLayout>
  21. <RelativeLayout
  22. android:id="@+id/right_menu"
  23. android:layout_width="270dip"
  24. android:layout_height="fill_parent"
  25. android:layout_alignParentRight="true"
  26. android:background="#00ffcc"
  27. android:visibility="invisible">
  28. <TextView
  29. android:layout_width="wrap_content"
  30. android:layout_height="wrap_content"
  31. android:layout_centerInParent="true"
  32. android:text="Thisisrightmenu"
  33. android:textColor="#000000"
  34. android:textSize="28sp"/>
  35. </RelativeLayout>
  36. <LinearLayout
  37. android:id="@+id/content"
  38. android:layout_width="320dip"
  39. android:layout_height="fill_parent"
  40. android:background="#e9e9e9">
  41. <ListView
  42. android:id="@+id/contentList"
  43. android:layout_width="fill_parent"
  44. android:layout_height="fill_parent"
  45. android:scrollbars="none"
  46. android:cacheColorHint="#00000000">
  47. </ListView>
  48. </LinearLayout>
  49. </com.example.bidirslidinglayout.BidirSlidingLayout>
可以看到,我们使用了自定义的BidirSlidingLayout作为根布局,然后依次加入了三个子布局分别作为左侧菜单、右侧菜单和内容的布局。左侧菜单和右侧菜单中都只是简单地放入了一个TextView用于显示一段文字,内容布局中放入了一个ListView。注意要让左侧菜单和父布局左边缘对齐,右侧菜单和父布局右边缘对齐。

最后打开或者创建MainActivity作为程序的主Activity,代码如下所示:

  1. publicclassMainActivityextendsActivity{
  2. /**
  3. *双向滑动菜单布局
  4. */
  5. privateBidirSlidingLayoutbidirSldingLayout;
  6. /**
  7. *在内容布局上显示的ListView
  8. */
  9. privateListViewcontentList;
  10. /**
  11. *ListView的适配器
  12. */
  13. privateArrayAdapter<String>contentListAdapter;
  14. /**
  15. *用于填充contentListAdapter的数据源。
  16. */
  17. privateString[]contentItems={"ContentItem1","ContentItem2","ContentItem3",
  18. "ContentItem4","ContentItem5","ContentItem6","ContentItem7",
  19. "ContentItem8","ContentItem9","ContentItem10","ContentItem11",
  20. "ContentItem12","ContentItem13","ContentItem14","ContentItem15",
  21. "ContentItem16"};
  22. @Override
  23. protectedvoidonCreate(BundlesavedInstanceState){
  24. super.onCreate(savedInstanceState);
  25. setContentView(R.layout.activity_main);
  26. bidirSldingLayout=(BidirSlidingLayout)findViewById(R.id.bidir_sliding_layout);
  27. contentList=(ListView)findViewById(R.id.contentList);
  28. contentListAdapter=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,
  29. contentItems);
  30. contentList.setAdapter(contentListAdapter);
  31. bidirSldingLayout.setScrollEvent(contentList);
  32. }
  33. }
这里我们给ListView填充了几条数据,又通过findViewById()方法获取到了BidirSlidingLayout对象,然后调用它的setScrollEvent()方法,将ListView进行绑定,这样就可以通过左右滑动ListView来展示左侧菜单和右侧菜单了。

好了,全部编码工作都已完成,现在让我们运行一下程序吧,效果如下图所示:


看起来还是挺不错的吧!并且更重要的是,以后我们在项目的任何地方都可以轻松加入双向滑动菜单功能,只需要以下两步即可:

1.在Acitivty的layout中引入我们自定义的BidirSlidingLayout布局,并且给这个布局要加入三个直接子元素。

2.在Activity中通过setScrollEvent方法,给一个View注册touch事件。

如此一来,一分钟实现双向滑动菜单功能妥妥的。

好了,今天的讲解到此结束,有疑问的朋友请在下面留言。

源码下载,请点击这里


转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9671609

记得在很早之前,我写了一篇关于Android滑动菜单的文章,其中有一个朋友在评论中留言,希望我可以帮他将这个滑动菜单改成双向滑动的方式。当时也没想花太多时间,简单修改了一下就发给了他,结果没想到后来却有一大批的朋友都来问我要这份双向滑动菜单的代码。由于这份代码写得很不用心,我发了部分朋友之后实在不忍心继续发下去了,于是决定专门写一篇文章来介绍更好的Android双向滑动菜单的实现方法。

在开始动手之前先来讲一下实现原理,在一个Activity的布局中需要有三部分,一个是左侧菜单的布局,一个是右侧菜单的布局,一个是内容布局。左侧菜单居屏幕左边缘对齐,右侧菜单居屏幕右边缘对齐,然后内容布局占满整个屏幕,并压在了左侧菜单和右侧菜单的上面。当用户手指向右滑动时,将右侧菜单隐藏,左侧菜单显示,然后通过偏移内容布局的位置,就可以让左侧菜单展现出来。同样的道理,当用户手指向左滑动时,将左侧菜单隐藏,右侧菜单显示,也是通过偏移内容布局的位置,就可以让右侧菜单展现出来。原理示意图所下所示:


介绍完了原理,我们就开始动手实现吧。新建一个Android项目,项目名就叫做BidirSlidingLayout。然后新建我们最主要的BidirSlidingLayout类,这个类就是实现双向滑动菜单功能的核心类,代码如下所示:

  1. publicclassBidirSlidingLayoutextendsRelativeLayoutimplementsOnTouchListener{
  2. /**
  3. *滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。
  4. */
  5. publicstaticfinalintSNAP_VELOCITY=200;
  6. /**
  7. *滑动状态的一种,表示未进行任何滑动。
  8. */
  9. publicstaticfinalintDO_NOTHING=0;
  10. /**
  11. *滑动状态的一种,表示正在滑出左侧菜单。
  12. */
  13. publicstaticfinalintSHOW_LEFT_MENU=1;
  14. /**
  15. *滑动状态的一种,表示正在滑出右侧菜单。
  16. */
  17. publicstaticfinalintSHOW_RIGHT_MENU=2;
  18. /**
  19. *滑动状态的一种,表示正在隐藏左侧菜单。
  20. */
  21. publicstaticfinalintHIDE_LEFT_MENU=3;
  22. /**
  23. *滑动状态的一种,表示正在隐藏右侧菜单。
  24. */
  25. publicstaticfinalintHIDE_RIGHT_MENU=4;
  26. /**
  27. *记录当前的滑动状态
  28. */
  29. privateintslideState;
  30. /**
  31. *屏幕宽度值。
  32. */
  33. privateintscreenWidth;
  34. /**
  35. *在被判定为滚动之前用户手指可以移动的最大值。
  36. */
  37. privateinttouchSlop;
  38. /**
  39. *记录手指按下时的横坐标。
  40. */
  41. privatefloatxDown;
  42. /**
  43. *记录手指按下时的纵坐标。
  44. */
  45. privatefloatyDown;
  46. /**
  47. *记录手指移动时的横坐标。
  48. */
  49. privatefloatxMove;
  50. /**
  51. *记录手指移动时的纵坐标。
  52. */
  53. privatefloatyMove;
  54. /**
  55. *记录手机抬起时的横坐标。
  56. */
  57. privatefloatxUp;
  58. /**
  59. *左侧菜单当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
  60. */
  61. privatebooleanisLeftMenuVisible;
  62. /**
  63. *右侧菜单当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
  64. */
  65. privatebooleanisRightMenuVisible;
  66. /**
  67. *是否正在滑动。
  68. */
  69. privatebooleanisSliding;
  70. /**
  71. *左侧菜单布局对象。
  72. */
  73. privateViewleftMenuLayout;
  74. /**
  75. *右侧菜单布局对象。
  76. */
  77. privateViewrightMenuLayout;
  78. /**
  79. *内容布局对象。
  80. */
  81. privateViewcontentLayout;
  82. /**
  83. *用于监听滑动事件的View。
  84. */
  85. privateViewmBindView;
  86. /**
  87. *左侧菜单布局的参数。
  88. */
  89. privateMarginLayoutParamsleftMenuLayoutParams;
  90. /**
  91. *右侧菜单布局的参数。
  92. */
  93. privateMarginLayoutParamsrightMenuLayoutParams;
  94. /**
  95. *内容布局的参数。
  96. */
  97. privateRelativeLayout.LayoutParamscontentLayoutParams;
  98. /**
  99. *用于计算手指滑动的速度。
  100. */
  101. privateVelocityTrackermVelocityTracker;
  102. /**
  103. *重写BidirSlidingLayout的构造函数,其中获取了屏幕的宽度和touchSlop的值。
  104. *
  105. *@paramcontext
  106. *@paramattrs
  107. */
  108. publicBidirSlidingLayout(Contextcontext,AttributeSetattrs){
  109. super(context,attrs);
  110. WindowManagerwm=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
  111. screenWidth=wm.getDefaultDisplay().getWidth();
  112. touchSlop=ViewConfiguration.get(context).getScaledTouchSlop();
  113. }
  114. /**
  115. *绑定监听滑动事件的View。
  116. *
  117. *@parambindView
  118. *需要绑定的View对象。
  119. */
  120. publicvoidsetScrollEvent(ViewbindView){
  121. mBindView=bindView;
  122. mBindView.setOnTouchListener(this);
  123. }
  124. /**
  125. *将界面滚动到左侧菜单界面,滚动速度设定为-30.
  126. */
  127. publicvoidscrollToLeftMenu(){
  128. newLeftMenuScrollTask().execute(-30);
  129. }
  130. /**
  131. *将界面滚动到右侧菜单界面,滚动速度设定为-30.
  132. */
  133. publicvoidscrollToRightMenu(){
  134. newRightMenuScrollTask().execute(-30);
  135. }
  136. /**
  137. *将界面从左侧菜单滚动到内容界面,滚动速度设定为30.
  138. */
  139. publicvoidscrollToContentFromLeftMenu(){
  140. newLeftMenuScrollTask().execute(30);
  141. }
  142. /**
  143. *将界面从右侧菜单滚动到内容界面,滚动速度设定为30.
  144. */
  145. publicvoidscrollToContentFromRightMenu(){
  146. newRightMenuScrollTask().execute(30);
  147. }
  148. /**
  149. *左侧菜单是否完全显示出来,滑动过程中此值无效。
  150. *
  151. *@return左侧菜单完全显示返回true,否则返回false。
  152. */
  153. publicbooleanisLeftLayoutVisible(){
  154. returnisLeftMenuVisible;
  155. }
  156. /**
  157. *右侧菜单是否完全显示出来,滑动过程中此值无效。
  158. *
  159. *@return右侧菜单完全显示返回true,否则返回false。
  160. */
  161. publicbooleanisRightLayoutVisible(){
  162. returnisRightMenuVisible;
  163. }
  164. /**
  165. *在onLayout中重新设定左侧菜单、右侧菜单、以及内容布局的参数。
  166. */
  167. @Override
  168. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  169. super.onLayout(changed,l,t,r,b);
  170. if(changed){
  171. //获取左侧菜单布局对象
  172. leftMenuLayout=getChildAt(0);
  173. leftMenuLayoutParams=(MarginLayoutParams)leftMenuLayout.getLayoutParams();
  174. //获取右侧菜单布局对象
  175. rightMenuLayout=getChildAt(1);
  176. rightMenuLayoutParams=(MarginLayoutParams)rightMenuLayout.getLayoutParams();
  177. //获取内容布局对象
  178. contentLayout=getChildAt(2);
  179. contentLayoutParams=(RelativeLayout.LayoutParams)contentLayout.getLayoutParams();
  180. contentLayoutParams.width=screenWidth;
  181. contentLayout.setLayoutParams(contentLayoutParams);
  182. }
  183. }
  184. @Override
  185. publicbooleanonTouch(Viewv,MotionEventevent){
  186. createVelocityTracker(event);
  187. switch(event.getAction()){
  188. caseMotionEvent.ACTION_DOWN:
  189. //手指按下时,记录按下时的坐标
  190. xDown=event.getRawX();
  191. yDown=event.getRawY();
  192. //将滑动状态初始化为DO_NOTHING
  193. slideState=DO_NOTHING;
  194. break;
  195. caseMotionEvent.ACTION_MOVE:
  196. xMove=event.getRawX();
  197. yMove=event.getRawY();
  198. //手指移动时,对比按下时的坐标,计算出移动的距离。
  199. intmoveDistanceX=(int)(xMove-xDown);
  200. intmoveDistanceY=(int)(yMove-yDown);
  201. //检查当前的滑动状态
  202. checkSlideState(moveDistanceX,moveDistanceY);
  203. //根据当前滑动状态决定如何偏移内容布局
  204. switch(slideState){
  205. caseSHOW_LEFT_MENU:
  206. contentLayoutParams.rightMargin=-moveDistanceX;
  207. checkLeftMenuBorder();
  208. contentLayout.setLayoutParams(contentLayoutParams);
  209. break;
  210. caseHIDE_LEFT_MENU:
  211. contentLayoutParams.rightMargin=-leftMenuLayoutParams.width-moveDistanceX;
  212. checkLeftMenuBorder();
  213. contentLayout.setLayoutParams(contentLayoutParams);
  214. caseSHOW_RIGHT_MENU:
  215. contentLayoutParams.leftMargin=moveDistanceX;
  216. checkRightMenuBorder();
  217. contentLayout.setLayoutParams(contentLayoutParams);
  218. break;
  219. caseHIDE_RIGHT_MENU:
  220. contentLayoutParams.leftMargin=-rightMenuLayoutParams.width+moveDistanceX;
  221. checkRightMenuBorder();
  222. contentLayout.setLayoutParams(contentLayoutParams);
  223. default:
  224. break;
  225. }
  226. break;
  227. caseMotionEvent.ACTION_UP:
  228. xUp=event.getRawX();
  229. intupDistanceX=(int)(xUp-xDown);
  230. if(isSliding){
  231. //手指抬起时,进行判断当前手势的意图
  232. switch(slideState){
  233. caseSHOW_LEFT_MENU:
  234. if(shouldScrollToLeftMenu()){
  235. scrollToLeftMenu();
  236. }else{
  237. scrollToContentFromLeftMenu();
  238. }
  239. break;
  240. caseHIDE_LEFT_MENU:
  241. if(shouldScrollToContentFromLeftMenu()){
  242. scrollToContentFromLeftMenu();
  243. }else{
  244. scrollToLeftMenu();
  245. }
  246. break;
  247. caseSHOW_RIGHT_MENU:
  248. if(shouldScrollToRightMenu()){
  249. scrollToRightMenu();
  250. }else{
  251. scrollToContentFromRightMenu();
  252. }
  253. break;
  254. caseHIDE_RIGHT_MENU:
  255. if(shouldScrollToContentFromRightMenu()){
  256. scrollToContentFromRightMenu();
  257. }else{
  258. scrollToRightMenu();
  259. }
  260. break;
  261. default:
  262. break;
  263. }
  264. }elseif(upDistanceX<touchSlop&&isLeftMenuVisible){
  265. //当左侧菜单显示时,如果用户点击一下内容部分,则直接滚动到内容界面
  266. scrollToContentFromLeftMenu();
  267. }elseif(upDistanceX<touchSlop&&isRightMenuVisible){
  268. //当右侧菜单显示时,如果用户点击一下内容部分,则直接滚动到内容界面
  269. scrollToContentFromRightMenu();
  270. }
  271. recycleVelocityTracker();
  272. break;
  273. }
  274. if(v.isEnabled()){
  275. if(isSliding){
  276. //正在滑动时让控件得不到焦点
  277. unFocusBindView();
  278. returntrue;
  279. }
  280. if(isLeftMenuVisible||isRightMenuVisible){
  281. //当左侧或右侧布局显示时,将绑定控件的事件屏蔽掉
  282. returntrue;
  283. }
  284. returnfalse;
  285. }
  286. returntrue;
  287. }
  288. /**
  289. *根据手指移动的距离,判断当前用户的滑动意图,然后给slideState赋值成相应的滑动状态值。
  290. *
  291. *@parammoveDistanceX
  292. *横向移动的距离
  293. *@parammoveDistanceY
  294. *纵向移动的距离
  295. */
  296. privatevoidcheckSlideState(intmoveDistanceX,intmoveDistanceY){
  297. if(isLeftMenuVisible){
  298. if(!isSliding&&Math.abs(moveDistanceX)>=touchSlop&&moveDistanceX<0){
  299. isSliding=true;
  300. slideState=HIDE_LEFT_MENU;
  301. }
  302. }elseif(isRightMenuVisible){
  303. if(!isSliding&&Math.abs(moveDistanceX)>=touchSlop&&moveDistanceX>0){
  304. isSliding=true;
  305. slideState=HIDE_RIGHT_MENU;
  306. }
  307. }else{
  308. if(!isSliding&&Math.abs(moveDistanceX)>=touchSlop&&moveDistanceX>0
  309. &&Math.abs(moveDistanceY)<touchSlop){
  310. isSliding=true;
  311. slideState=SHOW_LEFT_MENU;
  312. contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,0);
  313. contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
  314. contentLayout.setLayoutParams(contentLayoutParams);
  315. //如果用户想要滑动左侧菜单,将左侧菜单显示,右侧菜单隐藏
  316. leftMenuLayout.setVisibility(View.VISIBLE);
  317. rightMenuLayout.setVisibility(View.GONE);
  318. }elseif(!isSliding&&Math.abs(moveDistanceX)>=touchSlop&&moveDistanceX<0
  319. &&Math.abs(moveDistanceY)<touchSlop){
  320. isSliding=true;
  321. slideState=SHOW_RIGHT_MENU;
  322. contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,0);
  323. contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
  324. contentLayout.setLayoutParams(contentLayoutParams);
  325. //如果用户想要滑动右侧菜单,将右侧菜单显示,左侧菜单隐藏
  326. rightMenuLayout.setVisibility(View.VISIBLE);
  327. leftMenuLayout.setVisibility(View.GONE);
  328. }
  329. }
  330. }
  331. /**
  332. *在滑动过程中检查左侧菜单的边界值,防止绑定布局滑出屏幕。
  333. */
  334. privatevoidcheckLeftMenuBorder(){
  335. if(contentLayoutParams.rightMargin>0){
  336. contentLayoutParams.rightMargin=0;
  337. }elseif(contentLayoutParams.rightMargin<-leftMenuLayoutParams.width){
  338. contentLayoutParams.rightMargin=-leftMenuLayoutParams.width;
  339. }
  340. }
  341. /**
  342. *在滑动过程中检查右侧菜单的边界值,防止绑定布局滑出屏幕。
  343. */
  344. privatevoidcheckRightMenuBorder(){
  345. if(contentLayoutParams.leftMargin>0){
  346. contentLayoutParams.leftMargin=0;
  347. }elseif(contentLayoutParams.leftMargin<-rightMenuLayoutParams.width){
  348. contentLayoutParams.leftMargin=-rightMenuLayoutParams.width;
  349. }
  350. }
  351. /**
  352. *判断是否应该滚动将左侧菜单展示出来。如果手指移动距离大于左侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
  353. *就认为应该滚动将左侧菜单展示出来。
  354. *
  355. *@return如果应该将左侧菜单展示出来返回true,否则返回false。
  356. */
  357. privatebooleanshouldScrollToLeftMenu(){
  358. returnxUp-xDown>leftMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY;
  359. }
  360. /**
  361. *判断是否应该滚动将右侧菜单展示出来。如果手指移动距离大于右侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
  362. *就认为应该滚动将右侧菜单展示出来。
  363. *
  364. *@return如果应该将右侧菜单展示出来返回true,否则返回false。
  365. */
  366. privatebooleanshouldScrollToRightMenu(){
  367. returnxDown-xUp>rightMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY;
  368. }
  369. /**
  370. *判断是否应该从左侧菜单滚动到内容布局,如果手指移动距离大于左侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
  371. *就认为应该从左侧菜单滚动到内容布局。
  372. *
  373. *@return如果应该从左侧菜单滚动到内容布局返回true,否则返回false。
  374. */
  375. privatebooleanshouldScrollToContentFromLeftMenu(){
  376. returnxDown-xUp>leftMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY;
  377. }
  378. /**
  379. *判断是否应该从右侧菜单滚动到内容布局,如果手指移动距离大于右侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
  380. *就认为应该从右侧菜单滚动到内容布局。
  381. *
  382. *@return如果应该从右侧菜单滚动到内容布局返回true,否则返回false。
  383. */
  384. privatebooleanshouldScrollToContentFromRightMenu(){
  385. returnxUp-xDown>rightMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY;
  386. }
  387. /**
  388. *创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。
  389. *
  390. *@paramevent
  391. *右侧布局监听控件的滑动事件
  392. */
  393. privatevoidcreateVelocityTracker(MotionEventevent){
  394. if(mVelocityTracker==null){
  395. mVelocityTracker=VelocityTracker.obtain();
  396. }
  397. mVelocityTracker.addMovement(event);
  398. }
  399. /**
  400. *获取手指在绑定布局上的滑动速度。
  401. *
  402. *@return滑动速度,以每秒钟移动了多少像素值为单位。
  403. */
  404. privateintgetScrollVelocity(){
  405. mVelocityTracker.computeCurrentVelocity(1000);
  406. intvelocity=(int)mVelocityTracker.getXVelocity();
  407. returnMath.abs(velocity);
  408. }
  409. /**
  410. *回收VelocityTracker对象。
  411. */
  412. privatevoidrecycleVelocityTracker(){
  413. mVelocityTracker.recycle();
  414. mVelocityTracker=null;
  415. }
  416. /**
  417. *使用可以获得焦点的控件在滑动的时候失去焦点。
  418. */
  419. privatevoidunFocusBindView(){
  420. if(mBindView!=null){
  421. mBindView.setPressed(false);
  422. mBindView.setFocusable(false);
  423. mBindView.setFocusableInTouchMode(false);
  424. }
  425. }
  426. classLeftMenuScrollTaskextendsAsyncTask<Integer,Integer,Integer>{
  427. @Override
  428. protectedIntegerdoInBackground(Integer...speed){
  429. intrightMargin=contentLayoutParams.rightMargin;
  430. //根据传入的速度来滚动界面,当滚动到达边界值时,跳出循环。
  431. while(true){
  432. rightMargin=rightMargin+speed[0];
  433. if(rightMargin<-leftMenuLayoutParams.width){
  434. rightMargin=-leftMenuLayoutParams.width;
  435. break;
  436. }
  437. if(rightMargin>0){
  438. rightMargin=0;
  439. break;
  440. }
  441. publishProgress(rightMargin);
  442. //为了要有滚动效果产生,每次循环使线程睡眠一段时间,这样肉眼才能够看到滚动动画。
  443. sleep(15);
  444. }
  445. if(speed[0]>0){
  446. isLeftMenuVisible=false;
  447. }else{
  448. isLeftMenuVisible=true;
  449. }
  450. isSliding=false;
  451. returnrightMargin;
  452. }
  453. @Override
  454. protectedvoidonProgressUpdate(Integer...rightMargin){
  455. contentLayoutParams.rightMargin=rightMargin[0];
  456. contentLayout.setLayoutParams(contentLayoutParams);
  457. unFocusBindView();
  458. }
  459. @Override
  460. protectedvoidonPostExecute(IntegerrightMargin){
  461. contentLayoutParams.rightMargin=rightMargin;
  462. contentLayout.setLayoutParams(contentLayoutParams);
  463. }
  464. }
  465. classRightMenuScrollTaskextendsAsyncTask<Integer,Integer,Integer>{
  466. @Override
  467. protectedIntegerdoInBackground(Integer...speed){
  468. intleftMargin=contentLayoutParams.leftMargin;
  469. //根据传入的速度来滚动界面,当滚动到达边界值时,跳出循环。
  470. while(true){
  471. leftMargin=leftMargin+speed[0];
  472. if(leftMargin<-rightMenuLayoutParams.width){
  473. leftMargin=-rightMenuLayoutParams.width;
  474. break;
  475. }
  476. if(leftMargin>0){
  477. leftMargin=0;
  478. break;
  479. }
  480. publishProgress(leftMargin);
  481. //为了要有滚动效果产生,每次循环使线程睡眠一段时间,这样肉眼才能够看到滚动动画。
  482. sleep(15);
  483. }
  484. if(speed[0]>0){
  485. isRightMenuVisible=false;
  486. }else{
  487. isRightMenuVisible=true;
  488. }
  489. isSliding=false;
  490. returnleftMargin;
  491. }
  492. @Override
  493. protectedvoidonProgressUpdate(Integer...leftMargin){
  494. contentLayoutParams.leftMargin=leftMargin[0];
  495. contentLayout.setLayoutParams(contentLayoutParams);
  496. unFocusBindView();
  497. }
  498. @Override
  499. protectedvoidonPostExecute(IntegerleftMargin){
  500. contentLayoutParams.leftMargin=leftMargin;
  501. contentLayout.setLayoutParams(contentLayoutParams);
  502. }
  503. }
  504. /**
  505. *使当前线程睡眠指定的毫秒数。
  506. *
  507. *@parammillis
  508. *指定当前线程睡眠多久,以毫秒为单位
  509. */
  510. privatevoidsleep(longmillis){
  511. try{
  512. Thread.sleep(millis);
  513. }catch(InterruptedExceptione){
  514. e.printStackTrace();
  515. }
  516. }
  517. }
以上代码注释已经写得非常详细,我再来简单解释一下。首先在onLayout()方法中分别获取到左侧菜单、右侧菜单和内容布局的参数,并将内容布局的宽度重定义成屏幕的宽度,这样就可以保证内容布局既能覆盖住下面的菜单布局,还能偏移出屏幕。然后在onTouch()方法中监听触屏事件,以判断用户手势的意图。这里事先定义好了几种滑动状态,DO_NOTHING表示没有进行任何滑动,SHOW_LEFT_MENU表示用户想要滑出左侧菜单,SHOW_RIGHT_MENU表示用户想要滑出右侧菜单,HIDE_LEFT_MENU表示用户想要隐藏左侧菜单,HIDE_RIGHT_MENU表示用户想要隐藏右侧菜单,在checkSlideState()方法中判断出用户到底是想进行哪一种滑动操作,并给slideState变量赋值,然后根据slideState的值决定如何偏移内容布局。接着当用户手指离开屏幕时,会根据当前的滑动距离,决定后续的滚动方向,通过LeftMenuScrollTask和RightMenuScrollTask来完成完整的滑动过程。另外在滑动的过程,内容布局上的事件会被屏蔽掉,主要是通过一系列的return操作实现的,对这一部分不理解的朋友,请参阅Android事件分发机制完全解析,带你从源码的角度彻底理解

然后我们看一下setScrollEvent方法,这个方法接收一个View作为参数,然后为这个View绑定了一个touch事件。这是什么意思呢?让我们来想象一个场景,如果内容布局是一个LinearLayout,我可以通过监听LinearLayout上的touch事件来控制它的偏移。但是如果内容布局的LinearLayout里面加入了一个ListView,而这个ListView又充满了整个LinearLayout,这个时候LinearLayout将不可能再被touch到了,这个时候我们就需要将touch事件注册到ListView上。setScrollEvent方法也就是提供了一个注册接口,touch事件将会注册到传入的View上。

接下来打开或新建activity_main.xml文件,加入如下代码:

  1. <com.example.bidirslidinglayout.BidirSlidingLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:id="@+id/bidir_sliding_layout"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <RelativeLayout
  7. android:id="@+id/left_menu"
  8. android:layout_width="270dip"
  9. android:layout_height="fill_parent"
  10. android:layout_alignParentLeft="true"
  11. android:background="#00ccff"
  12. android:visibility="invisible">
  13. <TextView
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:layout_centerInParent="true"
  17. android:text="Thisisleftmenu"
  18. android:textColor="#000000"
  19. android:textSize="28sp"/>
  20. </RelativeLayout>
  21. <RelativeLayout
  22. android:id="@+id/right_menu"
  23. android:layout_width="270dip"
  24. android:layout_height="fill_parent"
  25. android:layout_alignParentRight="true"
  26. android:background="#00ffcc"
  27. android:visibility="invisible">
  28. <TextView
  29. android:layout_width="wrap_content"
  30. android:layout_height="wrap_content"
  31. android:layout_centerInParent="true"
  32. android:text="Thisisrightmenu"
  33. android:textColor="#000000"
  34. android:textSize="28sp"/>
  35. </RelativeLayout>
  36. <LinearLayout
  37. android:id="@+id/content"
  38. android:layout_width="320dip"
  39. android:layout_height="fill_parent"
  40. android:background="#e9e9e9">
  41. <ListView
  42. android:id="@+id/contentList"
  43. android:layout_width="fill_parent"
  44. android:layout_height="fill_parent"
  45. android:scrollbars="none"
  46. android:cacheColorHint="#00000000">
  47. </ListView>
  48. </LinearLayout>
  49. </com.example.bidirslidinglayout.BidirSlidingLayout>
可以看到,我们使用了自定义的BidirSlidingLayout作为根布局,然后依次加入了三个子布局分别作为左侧菜单、右侧菜单和内容的布局。左侧菜单和右侧菜单中都只是简单地放入了一个TextView用于显示一段文字,内容布局中放入了一个ListView。注意要让左侧菜单和父布局左边缘对齐,右侧菜单和父布局右边缘对齐。

最后打开或者创建MainActivity作为程序的主Activity,代码如下所示:

  1. publicclassMainActivityextendsActivity{
  2. /**
  3. *双向滑动菜单布局
  4. */
  5. privateBidirSlidingLayoutbidirSldingLayout;
  6. /**
  7. *在内容布局上显示的ListView
  8. */
  9. privateListViewcontentList;
  10. /**
  11. *ListView的适配器
  12. */
  13. privateArrayAdapter<String>contentListAdapter;
  14. /**
  15. *用于填充contentListAdapter的数据源。
  16. */
  17. privateString[]contentItems={"ContentItem1","ContentItem2","ContentItem3",
  18. "ContentItem4","ContentItem5","ContentItem6","ContentItem7",
  19. "ContentItem8","ContentItem9","ContentItem10","ContentItem11",
  20. "ContentItem12","ContentItem13","ContentItem14","ContentItem15",
  21. "ContentItem16"};
  22. @Override
  23. protectedvoidonCreate(BundlesavedInstanceState){
  24. super.onCreate(savedInstanceState);
  25. setContentView(R.layout.activity_main);
  26. bidirSldingLayout=(BidirSlidingLayout)findViewById(R.id.bidir_sliding_layout);
  27. contentList=(ListView)findViewById(R.id.contentList);
  28. contentListAdapter=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,
  29. contentItems);
  30. contentList.setAdapter(contentListAdapter);
  31. bidirSldingLayout.setScrollEvent(contentList);
  32. }
  33. }
这里我们给ListView填充了几条数据,又通过findViewById()方法获取到了BidirSlidingLayout对象,然后调用它的setScrollEvent()方法,将ListView进行绑定,这样就可以通过左右滑动ListView来展示左侧菜单和右侧菜单了。

好了,全部编码工作都已完成,现在让我们运行一下程序吧,效果如下图所示:


看起来还是挺不错的吧!并且更重要的是,以后我们在项目的任何地方都可以轻松加入双向滑动菜单功能,只需要以下两步即可:

1.在Acitivty的layout中引入我们自定义的BidirSlidingLayout布局,并且给这个布局要加入三个直接子元素。

2.在Activity中通过setScrollEvent方法,给一个View注册touch事件。

如此一来,一分钟实现双向滑动菜单功能妥妥的。

好了,今天的讲解到此结束,有疑问的朋友请在下面留言。

源码下载,请点击这里

分享到:
评论

相关推荐

    Android双向滑动菜单完全解析,教你如何一分钟实现双向滑动特效demo

    这篇教程将深入解析如何在Android中实现双向滑动菜单,并通过一个简单的DEMO来展示其实现过程。 首先,我们要理解双向滑动菜单的核心组件——滑动布局(Sliding Layout)。这个布局通常是一个自定义视图,它能够...

    Android滑动菜单框架完全解析,教你如何一分钟实现滑动菜单特效demo

    本教程将全面解析一个Android滑动菜单框架的实现过程,帮助开发者在一分钟内创建具有滑动菜单特效的demo。 首先,我们要理解滑动菜单的基本原理。滑动菜单通常是通过在主布局中添加一个覆盖整个屏幕的全屏布局,...

    Android双向滑动菜单带按钮版

    本文将详细解析"Android双向滑动菜单带按钮版"这一主题,结合标签"Android", "双向滑动", "滑动菜单", "slidemenu"和"特效",以及压缩包中的"BidirSlidingLayout"文件,我们将深入讨论如何实现一个具有双向滑动功能...

    Android 3D滑动菜单完全解析,实现推拉门式的立体特效Demo

    在博文《Android 3D滑动菜单完全解析》中,作者Guolin_blog详细介绍了如何实现这个过程,包括设置布局、处理手势、调整Camera以及优化性能等方面的内容。如果你在实现过程中遇到问题,可以通过链接到他的博客留言...

    Android滑动菜单框架完全解析

    本篇文章将全面解析Android滑动菜单框架的实现原理、常用库以及如何在实际项目中应用。 一、Android滑动菜单的重要性 滑动菜单提供了简洁且直观的用户交互方式,尤其适用于屏幕空间有限的移动设备。它可以帮助用户...

    Android实现双向滑动特效的实例代码

    在Android开发中,双向滑动特效通常用于实现类似抽屉式菜单的效果,允许用户从屏幕两侧滑动以显示或隐藏不同的菜单。以下是对标题和描述中提到的知识点的详细解释: 1. **滑动菜单(Sliding Menu)**:滑动菜单是一...

    Android滑动菜单框架完全解析,加入点击未隐藏部分回到主界面的功能

    本文将深入解析如何实现一个完整的滑动菜单框架,并特别讲解如何添加一个新功能:当用户在菜单界面点击未隐藏部分时,能够自动返回到主界面。 首先,我们关注的是"Android滑动菜单框架"。在Android中,常见的滑动...

    Android高级应用源码-Android双向滑动菜单带按钮版.zip

    这个"Android高级应用源码-Android双向滑动菜单带按钮版.zip"提供了这样一个实现,它包含了一个自定义布局`BidirSlidingLayout`,用于创建这种效果。下面我们将深入探讨相关知识点。 1. **自定义布局**: 自定义...

    Android双向表格可滑动可双向适配

    本项目“Android双向表格可滑动可双向适配”着重于实现这样的功能,并在此基础上增加了自定义表头以及横向适配的能力。这在展示复杂数据集时尤其有用,例如在电商应用的商品列表或财务软件的报表中。 1. **双向滚动...

    安卓SlidingMenu各种菜单侧滑菜单相关-Android双向滑动菜单带按钮版.zip

    "安卓SlidingMenu各种菜单侧滑菜单相关-Android双向滑动菜单带按钮版.zip"这个资源包包含了实现这种功能的相关代码,特别是支持双向滑动的版本,即用户可以从左右两侧滑动来显示不同的菜单。 **SlidingMenu** 是一...

    Android高级应用源码-Android双向滑动菜单带按钮版.rar

    本资源"Android高级应用源码-Android双向滑动菜单带按钮版.rar"提供了一个实现此类功能的示例代码,可以帮助开发者深入理解如何在自己的项目中实现这一特性。 1. **滑动菜单原理** 双向滑动菜单的核心是通过手势...

    Android实现3D推拉门式滑动菜单源码解析

    本文将深入解析如何在Android平台上实现这种3D效果的滑动菜单,以及如何优化源码以适应实际项目需求。 首先,3D推拉门式滑动菜单的核心在于通过Matrix和Camera类来实现视图的3D旋转效果。Matrix是Android图形库中...

    双向滑动菜单

    在IT行业中,双向滑动菜单是一种常见的用户界面(UI)设计模式,特别是在移动应用开发中。这种设计允许用户从屏幕的两侧向内滑动来显示不同的菜单或内容区域,为用户提供了一种直观且高效的交互方式。本文将深入探讨...

    Android双向侧滑菜单 自定义控件

    在Android应用开发中,侧滑菜单(DrawerLayout)是一种常见的设计模式,用于提供导航和功能选项。本实例代码着重讲解如何实现一个支持双向侧滑的自定义控件,即不仅可从左侧滑出,也能从右侧滑出。下面将详细阐述...

    Android左右滑动菜单

    在Android应用开发中,滑动菜单(Sliding Menu)是一种常见的设计模式,它允许用户通过在屏幕边缘滑动来展示或隐藏一个侧边栏菜单。这种设计为用户提供了一种直观的方式来访问更多的功能或选项,而不会占据屏幕的...

    Android滑动菜单特效实现,仿人人客户端侧滑效果demo程序源码

    Android滑动菜单的实现方式多种多样,而"Android滑动菜单特效实现,仿人人客户端侧滑效果demo程序源码"是针对这一主题的一个实例项目。这个示例代码旨在模仿人人客户端的侧滑菜单效果,提供了丰富的交互体验和视觉...

    双向滑动的范围选择条SeekBar

    标题中的“双向滑动的范围选择条SeekBar”指的是在Android开发中,一种允许用户通过滑动来选择一个数值范围的自定义控件。通常,SeekBar是Android提供的一个单向滑动条,用户可以左右滑动来改变某个值,但在这个场景...

    android圆形滑动菜单源码.zip

    这个"android圆形滑动菜单源码.zip"提供了实现此类菜单的详细代码,对于开发者来说,是一个极好的学习资源,特别是对于那些希望在自己的应用中加入新颖导航方式的安卓开发者。 首先,我们来理解一下"圆形滑动菜单...

    Android实现导航菜单左右滑动效果

    本文将详细讲解如何在Android平台上实现一个左右滑动的导航菜单,这种效果通常被用于展示多个主选项,用户可以通过平滑的手势在各个页面之间切换。 首先,实现这个功能的关键是使用`android.support.v4.view....

Global site tag (gtag.js) - Google Analytics