Android左右滑出菜单
发布日期:2021-07-29 10:44:43 浏览次数:6 分类:技术文章

本文共 24636 字,大约阅读时间需要 82 分钟。

现在的Android应用,只要有一个什么新的创意,过不了多久,几乎所有的应用都带这个创意。这不,咱们公司最近的一个持续性的项目,想在首页加个从左滑动出来的菜单,我查阅网上资料,并自己摸索,实现了左、右两边都能滑出菜单,并且,左、右菜单中,都可以加ListView等这类需要解决GestureDetector冲突的问题(如在首页面中,含有ListView,上下滚动时,左右不动,相反,左右滑动菜单时,上下不动,听着头就大了吧!)

先上几张图,给大家瞧瞧,对整体有个了解:

      

图1(首页)                         图2(左侧菜单+ListView)            图3(右侧菜单)

 一、首页布局:

[html] 
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context=".MainActivity" >  
  6.   
  7.     <!-- 主布局 -->  
  8.     <RelativeLayout   
  9.         android:id="@+id/mainLayout"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="match_parent">  
  12.         <RelativeLayout   
  13.             android:id="@+id/titlebar"  
  14.             android:layout_width="match_parent"  
  15.             android:layout_height="wrap_content"  
  16.             android:background="#ffffff"  
  17.             android:padding="5dip">  
  18.             <ImageView   
  19.                 android:id="@+id/ivMore"  
  20.                 android:src="@drawable/nav_more_normal"  
  21.                 android:contentDescription="@string/img_desc"  
  22.                 android:layout_width="wrap_content"  
  23.                 android:layout_height="wrap_content"  
  24.                 android:layout_centerVertical="true"  
  25.                 android:layout_alignParentLeft="true"  
  26.                 android:layout_marginLeft="10dip"/>  
  27.             <TextView   
  28.                 android:layout_width="wrap_content"  
  29.                 android:layout_height="wrap_content"  
  30.                 android:layout_centerHorizontal="true"  
  31.                 android:layout_centerVertical="true"  
  32.                 android:text="@string/title"  
  33.                 android:textSize="20sp"  
  34.                 android:textColor="#000000"/>  
  35.             <ImageView   
  36.                 android:id="@+id/ivSettings"  
  37.                 android:src="@drawable/nav_setting_normal"  
  38.                 android:contentDescription="@string/img_desc"  
  39.                 android:layout_width="wrap_content"  
  40.                 android:layout_height="wrap_content"  
  41.                 android:layout_centerVertical="true"  
  42.                 android:layout_alignParentRight="true"  
  43.                 android:layout_marginRight="10dip"/>  
  44.         </RelativeLayout>  
  45.           
  46.         <ImageView   
  47.             android:src="@drawable/picture"  
  48.             android:contentDescription="@string/img_desc"  
  49.             android:scaleType="fitXY"  
  50.             android:layout_width="match_parent"  
  51.             android:layout_height="match_parent"  
  52.             android:layout_below="@id/titlebar"/>  
  53.     </RelativeLayout>  
  54.       
  55.     <!-- 左侧菜单导航 -->  
  56.     <RelativeLayout   
  57.         android:id="@+id/leftLayout"  
  58.         android:layout_width="140dip"  
  59.         android:layout_height="match_parent"  
  60.         android:background="#000000">  
  61.         <RelativeLayout   
  62.             android:id="@+id/leftTitlebar"  
  63.             android:layout_width="match_parent"  
  64.             android:layout_height="wrap_content"  
  65.             android:background="@color/grey21"  
  66.             android:padding="5dip">  
  67.             <TextView   
  68.                 android:layout_marginLeft="5dip"  
  69.                 android:layout_width="wrap_content"  
  70.                 android:layout_height="wrap_content"  
  71.                 android:layout_alignParentLeft="true"  
  72.                 android:layout_centerVertical="true"  
  73.                 android:text="@string/leftNav"  
  74.                 android:textSize="20sp"  
  75.                 android:textColor="#ffffff"/>  
  76.         </RelativeLayout>  
  77.         <com.chris.lr.slidemenu.LayoutRelative  
  78.             android:id="@+id/layoutSlideMenu"  
  79.             android:layout_width="match_parent"  
  80.                  android:layout_height="match_parent"  
  81.             android:layout_below="@id/leftTitlebar">  
  82.             <ListView   
  83.                 android:id="@+id/listMore"  
  84.                 android:cacheColorHint="#00000000"  
  85.                 android:layout_width="match_parent"  
  86.                 android:layout_height="match_parent"/>  
  87.         </com.chris.lr.slidemenu.LayoutRelative>  
  88.     </RelativeLayout>  
  89.       
  90.     <!-- 右侧菜单导航 -->  
  91.     <RelativeLayout   
  92.         android:id="@+id/rightLayout"  
  93.         android:layout_width="140dip"  
  94.         android:layout_height="match_parent"  
  95.         android:background="#000000">  
  96.         <RelativeLayout   
  97.             android:id="@+id/rightTitlebar"  
  98.             android:layout_width="match_parent"  
  99.             android:layout_height="wrap_content"  
  100.             android:background="@color/gold"  
  101.             android:padding="5dip">  
  102.             <TextView   
  103.                 android:layout_marginLeft="5dip"  
  104.                 android:layout_width="wrap_content"  
  105.                 android:layout_height="wrap_content"  
  106.                 android:layout_alignParentLeft="true"  
  107.                 android:layout_centerVertical="true"  
  108.                 android:text="@string/right_title"  
  109.                 android:textSize="20sp"  
  110.                 android:textColor="#ffffff"/>  
  111.         </RelativeLayout>  
  112.           
  113.         <TextView   
  114.             android:text="@string/rightNav"  
  115.             android:textColor="#ff00ff"  
  116.             android:textSize="18sp"  
  117.             android:layout_width="match_parent"  
  118.             android:layout_height="30dip"  
  119.             android:layout_below="@id/rightTitlebar"  
  120.             android:background="#000000"/>  
  121.     </RelativeLayout>  
  122.   
  123. </RelativeLayout>  

布局很简单,我个人比较推荐用RelativeLayout,因为这个是几个Layout中,性能最好的,而LinearLayout则不好,原因在于,某个子视图的宽高变动,会引起这个布局中其它地方也需要重新调整。

布局中,有com.chris.lr.slidemenu.LayoutRelative这个自定义控件是继承RelativeLayout的,里面只是加了些手势的处理,它的作用实际上就是最开始讲到的,如果含有ListView这类需要判断手势的,则就用到它,先由它来判断,然后在视情况是否拦截由自己来处理。

二、自定义控件:

[java] 
  1. package com.chris.lr.slidemenu;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.util.Log;  
  6. import android.view.GestureDetector;  
  7. import android.view.GestureDetector.SimpleOnGestureListener;  
  8. import android.view.MotionEvent;  
  9. import android.widget.RelativeLayout;  
  10.   
  11. public class LayoutRelative extends RelativeLayout {  
  12.   
  13.     private static final String TAG = "ChrisSlideMenu";  
  14.     private GestureDetector mGestureDetector;  
  15.     private boolean bLockScrollX = false;  
  16.     private boolean bTouchIntercept = false;  
  17.       
  18.     private OnScrollListener mOnScrollListenerCallback = null;  
  19.       
  20.     public LayoutRelative(Context context) {  
  21.         this(context, null);  
  22.     }  
  23.       
  24.     public LayoutRelative(Context context, AttributeSet attrs) {  
  25.         this(context, attrs, 0);  
  26.     }  
  27.       
  28.     public LayoutRelative(Context context, AttributeSet attrs, int defStyle) {  
  29.         super(context, attrs, defStyle);  
  30.           
  31.         mGestureDetector = new GestureDetector(new LayoutGestureListener());  
  32.     }  
  33.       
  34.     /** 
  35.      *  设置滚动监听接口 
  36.      *  @param l 
  37.      */  
  38.     public void setOnScrollListener(OnScrollListener l){  
  39.         mOnScrollListenerCallback = l;  
  40.     }  
  41.       
  42.     /* 
  43.      * Progress: 
  44.      * 1. 重载dispatchTouchEvent,将消息传递给GestureDetector; 
  45.      * 2. 重载手势中onDown 和 onScroll两个函数; 
  46.      * 3. 在onDown中,默认对水平滚动方向加锁; 
  47.      * 4. 在onScroll中,判断e1与e2的水平方向与垂直方向距离: 
  48.      *    a. 如果垂直方向大,则表明是上下滚动,且返回false表明当前手势不用拦截; 
  49.      *    b. 如果水平方向大,则表明是左右滚动,且返回true表明当前手势需要拦截; 
  50.      * 5. 重载onInterceptTouchEvent,如果手势返回为true,则onInterceptTouchEvent也返回true; 
  51.      * 6. 如果要拦截手势消息,则需要重载onTouchEvent,或子视图中重载这个函数,来处理这条消息; 
  52.      * 7. 如果自己处理,则对水平方向滚动去锁(表明当前用户想左右滚动); 
  53.      *  
  54.      *                                     ---------- 
  55.      *  ----------------------      ------>| onDown | 
  56.      *  |                    |      |      ---------- 
  57.      *  | dispatchTouchEvent | <----                         ------ false: 上下滚动 
  58.      *  |                    |      |      ------------     / 
  59.      *  ----------------------      ------>| onScroll | ---- 
  60.      *            |                        ------------     \ 
  61.      *            |                                          ------ true : 左右滚动 
  62.      *            |   intercept = true   ---------------- 
  63.      *            |----------------------| onTouchEvent | 
  64.      *            |                      ---------------- 
  65.      * ------------------------- 
  66.      * |                       | 
  67.      * | onInterceptTouchEvent | 
  68.      * |                       | 
  69.      * ------------------------- 
  70.      * 
  71.      */  
  72.     @Override  
  73.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  74.         bTouchIntercept = mGestureDetector.onTouchEvent(ev);  
  75.           
  76.         if(MotionEvent.ACTION_UP == ev.getAction() && !bLockScrollX){  
  77.             if(mOnScrollListenerCallback != null){  
  78.                 mOnScrollListenerCallback.doOnRelease();  
  79.             }  
  80.         }  
  81.           
  82.         return super.dispatchTouchEvent(ev);  
  83.     }  
  84.       
  85.     // viewgroup.onInterceptTouchEvent  
  86.     @Override  
  87.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  88.         super.onInterceptTouchEvent(ev);  
  89.         return bTouchIntercept;  
  90.     }  
  91.   
  92.     // view.onTouchEvent  
  93.     @Override  
  94.     public boolean onTouchEvent(MotionEvent event) {  
  95.         bLockScrollX = false;  
  96.         return super.onTouchEvent(event);  
  97.     }  
  98.   
  99.     /** 
  100.      *  @author cheng.yang 
  101.      * 
  102.      *  自定义手势监听 
  103.      */  
  104.     public class LayoutGestureListener extends SimpleOnGestureListener {  
  105.   
  106.         @Override  
  107.         public boolean onDown(MotionEvent e) {  
  108.             bLockScrollX = true;  
  109.             return super.onDown(e);  
  110.         }  
  111.   
  112.         @Override  
  113.         public boolean onScroll(MotionEvent e1, MotionEvent e2,  
  114.                 float distanceX, float distanceY) {  
  115.             if(!bLockScrollX){  
  116.                 if(mOnScrollListenerCallback != null){  
  117.                     mOnScrollListenerCallback.doOnScroll(e1, e2, distanceX, distanceY);  
  118.                 }  
  119.             }  
  120.               
  121.             if(Math.abs(e1.getY() - e2.getY()) > Math.abs(e1.getX() - e2.getX())){  
  122.                 return false;  
  123.             }else{  
  124.                 return true;  
  125.             }  
  126.         }  
  127.     }  
  128.       
  129.     public interface OnScrollListener {  
  130.         void doOnScroll(MotionEvent e1, MotionEvent e2,   
  131.                 float distanceX, float distanceY);  
  132.         void doOnRelease();  
  133.     }  
  134. }  

这个控件中,含有一个接口,当用户手势为左右时,则需要将滚动数据回传到主视图中去处理,而自己拦截不往下传递消息。里面有个消息流程图,讲的比较详细了,大家可以先看看,有什么问题,可以问我。

三、MainActivity的实现:

这个需要着讲解,毕竟,左、右滑动的实现都在这里。

[java] 
  1. /** 
  2.  *  
  3.  * @author cheng.yang 
  4.  * 
  5.  *  左、右菜单滑出 
  6.  * 
  7.  *  params[0]: 滑动距离 
  8.  *  params[1]: 滑动速度,带方向 
  9.  */  
  10. public class SlideMenu extends AsyncTask<Integer, Integer, Void>{  
  11.     @Override  
  12.     protected Void doInBackground(Integer... params) {  
  13.         if(params.length != 2){  
  14.             Log.e(TAG, "error, params must have 2!");  
  15.         }  
  16.   
  17.         int times = params[0] / Math.abs(params[1]);  
  18.         if(params[0] % Math.abs(params[1]) != 0){  
  19.             times ++;  
  20.         }  
  21.           
  22.         for(int i = 0; i < times; i++){  
  23.             this.publishProgress(params[0], params[1], i+1);  
  24.         }  
  25.           
  26.         return null;  
  27.     }  
  28.   
  29.     @Override  
  30.     protected void onProgressUpdate(Integer... values) {  
  31.         if(values.length != 3){  
  32.             Log.e(TAG, "error, values must have 3!");  
  33.         }  
  34.   
  35.         int distance = Math.abs(values[1]) * values[2];  
  36.         int delta = values[0] - distance;  
  37.   
  38.         int leftMargin = 0;  
  39.         if(values[1] < 0){ // 左移  
  40.             leftMargin = (delta > 0 ? values[1] : -(Math.abs(values[1]) - Math.abs(delta)));  
  41.         }else{  
  42.             leftMargin = (delta > 0 ? values[1] : (Math.abs(values[1]) - Math.abs(delta)));  
  43.         }  
  44.           
  45.         rollLayout(leftMargin);  
  46.     }  

首先,自定义一个继承于AsyncTask的类的线程,这个自定义类,就是用来实现动画效果,重在“滑动”,而不是硬生生的挤出来。关于AsyncTask的用法,大家可以看我的博客中关于它的讲解:

 

自定义类的使用需要两个参数,一个是滑动的距离,一个是每次滑动多少且向哪个方向滑动:

1. 滑动距离:若是左侧菜单滑出来,距离就是左侧菜单的宽度;同理,右侧滑出就是右侧菜单的宽度;

2. 滑动速度:即动画滑动时,向哪个方向,且每次滑动多少像素;

在doInBackground中,计算需要滑动多少次,然后用for循环调用publishProgress,实际上就是调用的onProgressUpdate,在onProgressUpdate中,根据方向,以及当前main layout的 leftMargin来计算是滑动指定的距离(速度),还是当终点距离小于滑动速度时,速度就为终点距离,最终,调用rollLayout,来修改 leftLayout, mainLayout, rightLayout三者的布局,达到滑动的效果。

rollLayout的实现:

[java] 
  1. private void rollLayout(int margin){  
  2.     RelativeLayout.LayoutParams lp = (LayoutParams) mainLayout.getLayoutParams();  
  3.     lp.leftMargin += margin;  
  4.     lp.rightMargin -= margin;  
  5.     mainLayout.setLayoutParams(lp);  
  6.     lp = (LayoutParams) leftLayout.getLayoutParams();  
  7.     lp.leftMargin += margin;  
  8.     leftLayout.setLayoutParams(lp);  
  9.     lp = (LayoutParams) rightLayout.getLayoutParams();  
  10.     lp.leftMargin += margin;  
  11.     lp.rightMargin -= margin;  
  12.     rightLayout.setLayoutParams(lp);  
  13. }  

这个就是修改三个layout的leftMargin和rightMargin。

初始化布局控件:

[java] 
  1. private void initView(){  
  2.     mainLayout = (RelativeLayout) findViewById(R.id.mainLayout);  
  3.     leftLayout = (RelativeLayout) findViewById(R.id.leftLayout);  
  4.     rightLayout = (RelativeLayout) findViewById(R.id.rightLayout);  
  5.     mainLayout.setOnTouchListener(this);  
  6.     leftLayout.setOnTouchListener(this);  
  7.     rightLayout.setOnTouchListener(this);  
  8.       
  9.     layoutSlideMenu = (LayoutRelative) findViewById(R.id.layoutSlideMenu);  
  10.     layoutSlideMenu.setOnScrollListener(new OnScrollListener(){  
  11.         @Override  
  12.         public void doOnScroll(MotionEvent e1, MotionEvent e2,  
  13.                 float distanceX, float distanceY) {  
  14.             onScroll(distanceX);  
  15.         }  
  16.           
  17.         @Override  
  18.         public void doOnRelease(){  
  19.             onRelease();  
  20.         }  
  21.     });  
  22.       
  23.     ivMore = (ImageView) findViewById(R.id.ivMore);  
  24.     ivSettings = (ImageView) findViewById(R.id.ivSettings);  
  25.     ivMore.setOnTouchListener(this);  
  26.     ivSettings.setOnTouchListener(this);  
  27.       
  28.     mListMore = (ListView) findViewById(R.id.listMore);  
  29.     mListMore.setAdapter(new ArrayAdapter<String>(this, R.layout.item, R.id.tv_item, title));  
  30.     mListMore.setOnItemClickListener(this);  
  31.       
  32.     mGestureDetector = new GestureDetector(this);  
  33.     mGestureDetector.setIsLongpressEnabled(false);  
  34.       
  35.     resizeLayout();  
  36. }  

调整三个layout,将leftLayout移动到屏幕最左边之外,现时将rightLayout移动到屏幕最右边之外:

[java] 
  1. /* 
  2.  * 使用leftMargin及rightMargin防止layout被挤压变形 
  3.  * Math.abs(leftMargin - rightMargin) = layout.width 
  4.  */  
  5. private void resizeLayout(){  
  6.     DisplayMetrics dm = getResources().getDisplayMetrics();  
  7.       
  8.     // 固定 main layout, 防止被左、右挤压变形  
  9.     RelativeLayout.LayoutParams lp = (LayoutParams) mainLayout.getLayoutParams();  
  10.     lp.width = dm.widthPixels;  
  11.     mainLayout.setLayoutParams(lp);  
  12.       
  13.     // 将左layout调整至main layout左边  
  14.     lp = (LayoutParams) leftLayout.getLayoutParams();  
  15.     lp.leftMargin = -lp.width;  
  16.     leftLayout.setLayoutParams(lp);  
  17.     Log.d(TAG, "left l.margin = " + lp.leftMargin);  
  18.       
  19.     // 将左layout调整至main layout右边  
  20.     lp = (LayoutParams) rightLayout.getLayoutParams();  
  21.     lp.leftMargin = dm.widthPixels;  
  22.     lp.rightMargin = -lp.width;  
  23.     rightLayout.setLayoutParams(lp);  
  24.     Log.d(TAG, "right l.margin = " + lp.leftMargin);  
  25. }  

重载onTouch,处理消息:

[java] 
  1. // onTouch ///  
  2. @Override  
  3. public boolean onTouch(View v, MotionEvent event) {  
  4.     mClickedView = v;  
  5.       
  6.     if(MotionEvent.ACTION_UP == event.getAction() && bIsScrolling){  
  7.         onRelease();  
  8.     }  
  9.       
  10.     return mGestureDetector.onTouchEvent(event);  
  11. }  

记录选择的view,并将消息传给GestureDetector;

重载GestureDetector的onDown, onScroll, onSingleTapUp这三个是主要的,其它的重载不做任何修改;

[java] 
  1. /// GestureDetector Override Begin ///  
  2. @Override  
  3. public boolean onDown(MotionEvent e) {  
  4.       
  5.     bIsScrolling = false;  
  6.     mScroll = 0;  
  7.     iLimited = 0;  
  8.     RelativeLayout.LayoutParams lp = (LayoutParams) mainLayout.getLayoutParams();  
  9.     if(lp.leftMargin > 0){  
  10.         iLimited = 1;  
  11.     }else if(lp.leftMargin < 0){  
  12.         iLimited = -1;  
  13.     }  
  14.       
  15.     return true;  
  16. }  

在onDown中,判断当前的 mainLayout 的 leftMargin 的值,并用 iLimited 记录下来,原因:

如果当前显示的是左侧菜单,则用户滚动屏幕时,最多只是将左侧菜单滑出到屏幕外,而不会继续滑动,将右侧菜单显示出来;同理,当前已经显示了右侧菜单。

[java] 
  1. @Override  
  2. public boolean onSingleTapUp(MotionEvent e) {  
  3.     /* 
  4.      *  正常情况下,mainLayout的leftMargin为0, 
  5.      *  当左/右菜单为打开中,此时就不为0,需要判断 
  6.      */  
  7.     if(mClickedView != null){  
  8.         RelativeLayout.LayoutParams lp = (LayoutParams) mainLayout.getLayoutParams();  
  9.           
  10.         if(mClickedView == ivMore){  
  11.             Log.d(TAG, "[onSingleTapUp] ivMore clicked! leftMargin = " + lp.leftMargin);  
  12.               
  13.             if(lp.leftMargin == 0){  
  14.                 new SlideMenu().execute(leftLayout.getLayoutParams().width, SPEED);  
  15.             }else{  
  16.                 new SlideMenu().execute(leftLayout.getLayoutParams().width, -SPEED);  
  17.             }  
  18.         }else if(mClickedView == ivSettings){  
  19.             Log.d(TAG, "[onSingleTapUp] ivSettings clicked! leftMargin = " + lp.leftMargin);  
  20.               
  21.             if(lp.leftMargin == 0){  
  22.                 new SlideMenu().execute(rightLayout.getLayoutParams().width, -SPEED);  
  23.             }else{  
  24.                 new SlideMenu().execute(rightLayout.getLayoutParams().width, SPEED);  
  25.             }  
  26.         }else if(mClickedView == mainLayout){  
  27.             Log.d(TAG, "[onSingleTapUp] mainLayout clicked!");  
  28.         }  
  29.     }  
  30.     return true;  
  31. }  

这个函数中,处理标题栏左、右两个按钮,点击一次,显示侧导航菜单,再点击一次,则关闭。

[java] 
  1. @Override  
  2. public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,  
  3.         float distanceY) {  
  4.     onScroll(distanceX);  
  5.     return false;  
  6. }  

滚动处理,直接将本次的水平滚动距离传给onScroll函数来处理,因为,侧边导航菜单的水平滚动也将通过OnScrollListener.doOnScroll来回调,所以,写个通用函数。

[java] 
  1. private void onScroll(float distanceX){  
  2.     bIsScrolling = true;  
  3.     mScroll += distanceX;  // 向左为正  
  4.     Log.d(TAG, "mScroll = " + mScroll + ", distanceX = " + distanceX);  
  5.       
  6.     RelativeLayout.LayoutParams lp = (LayoutParams) mainLayout.getLayoutParams();  
  7.     RelativeLayout.LayoutParams lpLeft = (LayoutParams) leftLayout.getLayoutParams();  
  8.     RelativeLayout.LayoutParams lpRight = (LayoutParams) rightLayout.getLayoutParams();  
  9.     Log.d(TAG, "lp.leftMargin = " + lp.leftMargin);  
  10.       
  11.     int distance = 0;  
  12.     if(mScroll > 0){ // 向左移动  
  13.         if(lp.leftMargin <= 0){ // 打开右导航菜单  
  14.             if(iLimited > 0){  
  15.                 return;  
  16.             }  
  17.             distance = lpRight.width - Math.abs(lp.leftMargin);  
  18.         }else if(lp.leftMargin > 0){ // 关闭左导航菜单  
  19.             distance = lp.leftMargin;  
  20.         }  
  21.         if(mScroll >= distance){  
  22.             mScroll = distance;  
  23.         }  
  24.     }else if(mScroll < 0){  // 向右移动  
  25.         if(lp.leftMargin >= 0){ // 打开左导航菜单  
  26.             if(iLimited < 0){  
  27.                 return;  
  28.             }  
  29.             distance = lpLeft.width - Math.abs(lp.leftMargin);  
  30.         }else if(lp.leftMargin < 0){ // 关闭右导航菜单  
  31.             distance = Math.abs(lp.leftMargin);  
  32.         }  
  33.         if(mScroll <= -distance){  
  34.             mScroll = -distance;  
  35.         }  
  36.     }  
  37.   
  38.     Log.d(TAG, "mScroll = " + mScroll);  
  39.     if(mScroll != 0){  
  40.         rollLayout(-mScroll);  
  41.     }  
  42. }  

接下来,我们看看当侧边导航菜单,水平滚动且用户松开手指时,回调OnScrollListener.doOnRelease,我们会调用一个onRelease来负责处理收尾工作:

[java] 
  1. private void onRelease(){  
  2.     RelativeLayout.LayoutParams lp = (LayoutParams) mainLayout.getLayoutParams();  
  3.     if(lp.leftMargin < 0){ // 左移  
  4.         /* 
  5.          *  左移大于右导航宽度一半,则自动展开,否则自动缩回去 
  6.          */  
  7.         if(Math.abs(lp.leftMargin) > rightLayout.getLayoutParams().width/2){  
  8.             new SlideMenu().execute(rightLayout.getLayoutParams().width - Math.abs(lp.leftMargin), -SPEED);  
  9.         }else{  
  10.             new SlideMenu().execute(Math.abs(lp.leftMargin), SPEED);  
  11.         }  
  12.     }else if(lp.leftMargin > 0){  
  13.         /* 
  14.          *  右移大于左导航宽度一半,则自动展开,否则自动缩回去 
  15.          */  
  16.         if(Math.abs(lp.leftMargin) > leftLayout.getLayoutParams().width/2){  
  17.             new SlideMenu().execute(leftLayout.getLayoutParams().width - Math.abs(lp.leftMargin), SPEED);  
  18.         }else{  
  19.             new SlideMenu().execute(Math.abs(lp.leftMargin), -SPEED);  
  20.         }  
  21.     }  
  22. }  

主要的代码块已经讲解完了,下面是完整的代码:

[java] 
  1. package com.chris.lr.slidemenu;  
  2.   
  3. import com.chris.lr.slidemenu.LayoutRelative.OnScrollListener;  
  4.   
  5. import android.os.AsyncTask;  
  6. import android.os.Bundle;  
  7. import android.util.DisplayMetrics;  
  8. import android.util.Log;  
  9. import android.view.GestureDetector;  
  10. import android.view.GestureDetector.OnGestureListener;  
  11. import android.view.KeyEvent;  
  12. import android.view.MotionEvent;  
  13. import android.view.View;  
  14. import android.view.View.OnTouchListener;  
  15. import android.view.Window;  
  16. import android.widget.AdapterView;  
  17. import android.widget.AdapterView.OnItemClickListener;  
  18. import android.widget.ArrayAdapter;  
  19. import android.widget.ImageView;  
  20. import android.widget.ListView;  
  21. import android.widget.RelativeLayout;  
  22. import android.widget.Toast;  
  23. import android.widget.RelativeLayout.LayoutParams;  
  24. import android.app.Activity;  
  25.   
  26. public class MainActivity extends Activity implements OnGestureListener,   
  27.     OnTouchListener, OnItemClickListener {  
  28.   
  29.     private static final String TAG = "ChrisSlideMenu";  
  30.     private RelativeLayout mainLayout;  
  31.     private RelativeLayout leftLayout;  
  32.     private RelativeLayout rightLayout;  
  33.     private LayoutRelative layoutSlideMenu;  
  34.     private ListView mListMore;  
  35.       
  36.     private ImageView ivMore;  
  37.     private ImageView ivSettings;  
  38.     private GestureDetector mGestureDetector;  
  39.       
  40.     private static final int SPEED = 30;  
  41.     private boolean bIsScrolling = false;  
  42.     private int iLimited = 0;  
  43.     private int mScroll = 0;  
  44.     private View mClickedView = null;  
  45.       
  46.     private String title[] = {
    "待发送队列",   
  47.                               "同步分享设置",   
  48.                               "编辑我的资料",   
  49.                               "找朋友",   
  50.                               "告诉朋友",   
  51.                               "节省流量",   
  52.                               "推送设置",   
  53.                               "版本更新",   
  54.                               "意见反馈",   
  55.                               "积分兑换",   
  56.                               "精品应用",   
  57.                               "常见问题",   
  58.                               "退出当前帐号",   
  59.                               "退出1",   
  60.                               "退出2",   
  61.                               "退出3",   
  62.                               "退出4"};  
  63.       
  64.     @Override  
  65.     protected void onCreate(Bundle savedInstanceState) {  
  66.         super.onCreate(savedInstanceState);  
  67.         this.requestWindowFeature(Window.FEATURE_NO_TITLE);  
  68.         setContentView(R.layout.activity_main);  
  69.         initView();  
  70.     }  
  71.       
  72.     private void initView(){  
  73.         mainLayout = (RelativeLayout) findViewById(R.id.mainLayout);  
  74.         leftLayout = (RelativeLayout) findViewById(R.id.leftLayout);  
  75.         rightLayout = (RelativeLayout) findViewById(R.id.rightLayout);  
  76.         mainLayout.setOnTouchListener(this);  
  77.         leftLayout.setOnTouchListener(this);  
  78.         rightLayout.setOnTouchListener(this);  
  79.           
  80.         layoutSlideMenu = (LayoutRelative) findViewById(R.id.layoutSlideMenu);  
  81.         layoutSlideMenu.setOnScrollListener(new OnScrollListener(){  
  82.             @Override  
  83.             public void doOnScroll(MotionEvent e1, MotionEvent e2,  
  84.                     float distanceX, float distanceY) {  
  85.                 onScroll(distanceX);  
  86.             }  
  87.               
  88.             @Override  
  89.             public void doOnRelease(){  
  90.                 onRelease();  
  91.             }  
  92.         });  
  93.           
  94.         ivMore = (ImageView) findViewById(R.id.ivMore);  
  95.         ivSettings = (ImageView) findViewById(R.id.ivSettings);  
  96.         ivMore.setOnTouchListener(this);  
  97.         ivSettings.setOnTouchListener(this);  
  98.           
  99.         mListMore = (ListView) findViewById(R.id.listMore);  
  100.         mListMore.setAdapter(new ArrayAdapter<String>(  
  101.                 this, R.layout.item, R.id.tv_item, title));  
  102.         mListMore.setOnItemClickListener(this);  
  103.           
  104.         mGestureDetector = new GestureDetector(this);  
  105.         mGestureDetector.setIsLongpressEnabled(false);  
  106.           
  107.         resizeLayout();  
  108.     }  
  109.       
  110.     /* 
  111.      * 使用leftMargin及rightMargin防止layout被挤压变形 
  112.      * Math.abs(leftMargin - rightMargin) = layout.width 
  113.      */  
  114.     private void resizeLayout(){  
  115.         DisplayMetrics dm = getResources().getDisplayMetrics();  
  116.           
  117.         // 固定 main layout, 防止被左、右挤压变形  
  118.         RelativeLayout.LayoutParams lp =   
  119.             (LayoutParams) mainLayout.getLayoutParams();  
  120.         lp.width = dm.widthPixels;  
  121.         mainLayout.setLayoutParams(lp);  
  122.           
  123.         // 将左layout调整至main layout左边  
  124.         lp = (LayoutParams) leftLayout.getLayoutParams();  
  125.         lp.leftMargin = -lp.width;  
  126.         leftLayout.setLayoutParams(lp);  
  127.         Log.d(TAG, "left l.margin = " + lp.leftMargin);  
  128.           
  129.         // 将左layout调整至main layout右边  
  130.         lp = (LayoutParams) rightLayout.getLayoutParams();  
  131.         lp.leftMargin = dm.widthPixels;  
  132.         lp.rightMargin = -lp.width;  
  133.         rightLayout.setLayoutParams(lp);  
  134.         Log.d(TAG, "right l.margin = " + lp.leftMargin);  
  135.     }  
  136.       
  137.     @Override  
  138.     public boolean onKeyDown(int keyCode, KeyEvent event) {  
  139.         if(keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0){  
  140.             RelativeLayout.LayoutParams lp =   
  141.                 (LayoutParams) mainLayout.getLayoutParams();  
  142.               
  143.             if(lp.leftMargin != 0){  
  144.                 if(lp.leftMargin > 0){  
  145.                     new SlideMenu().execute(  
  146.                             leftLayout.getLayoutParams().width, -SPEED);  
  147.                 }else if(lp.leftMargin < 0){  
  148.                     new SlideMenu().execute(  
  149.                             rightLayout.getLayoutParams().width, SPEED);  
  150.                 }  
  151.                 return true;  
  152.             }  
  153.         }  
  154.         return super.onKeyDown(keyCode, event);  
  155.     }  
  156.       
  157.     private void rollLayout(int margin){  
  158.         RelativeLayout.LayoutParams lp =   
  159.             (LayoutParams) mainLayout.getLayoutParams();  
  160.         lp.leftMargin += margin;  
  161.         lp.rightMargin -= margin;  
  162.         mainLayout.setLayoutParams(lp);  
  163.         lp = (LayoutParams) leftLayout.getLayoutParams();  
  164.         lp.leftMargin += margin;  
  165.         leftLayout.setLayoutParams(lp);  
  166.         lp = (LayoutParams) rightLayout.getLayoutParams();  
  167.         lp.leftMargin += margin;  
  168.         lp.rightMargin -= margin;  
  169.         rightLayout.setLayoutParams(lp);  
  170.     }  
  171.   
  172.     private void onScroll(float distanceX){  
  173.         bIsScrolling = true;  
  174.         mScroll += distanceX;  // 向左为正  
  175.         Log.d(TAG, "mScroll = " + mScroll + ", distanceX = " + distanceX);  
  176.           
  177.         RelativeLayout.LayoutParams lp =   
  178.             (LayoutParams) mainLayout.getLayoutParams();  
  179.         RelativeLayout.LayoutParams lpLeft =   
  180.             (LayoutParams) leftLayout.getLayoutParams();  
  181.         RelativeLayout.LayoutParams lpRight =   
  182.             (LayoutParams) rightLayout.getLayoutParams();  
  183.         Log.d(TAG, "lp.leftMargin = " + lp.leftMargin);  
  184.           
  185.         int distance = 0;  
  186.         if(mScroll > 0){ // 向左移动  
  187.             if(lp.leftMargin <= 0){ // 打开右导航菜单  
  188.                 if(iLimited > 0){  
  189.                     return;  
  190.                 }  
  191.                 distance = lpRight.width - Math.abs(lp.leftMargin);  
  192.             }else if(lp.leftMargin > 0){ // 关闭左导航菜单  
  193.                 distance = lp.leftMargin;  
  194.             }  
  195.             if(mScroll >= distance){  
  196.                 mScroll = distance;  
  197.             }  
  198.         }else if(mScroll < 0){  // 向右移动  
  199.             if(lp.leftMargin >= 0){ // 打开左导航菜单  
  200.                 if(iLimited < 0){  
  201.                     return;  
  202.                 }  
  203.                 distance = lpLeft.width - Math.abs(lp.leftMargin);  
  204.             }else if(lp.leftMargin < 0){ // 关闭右导航菜单  
  205.                 distance = Math.abs(lp.leftMargin);  
  206.             }  
  207.             if(mScroll <= -distance){  
  208.                 mScroll = -distance;  
  209.             }  
  210.         }  
  211.   
  212.         Log.d(TAG, "mScroll = " + mScroll);  
  213.         if(mScroll != 0){  
  214.             rollLayout(-mScroll);  
  215.         }  
  216.     }  
  217.       
  218.     private void onRelease(){  
  219.         RelativeLayout.LayoutParams lp =   
  220.             (LayoutParams) mainLayout.getLayoutParams();  
  221.         if(lp.leftMargin < 0){ // 左移  
  222.             /* 
  223.              *  左移大于右导航宽度一半,则自动展开,否则自动缩回去 
  224.              */  
  225.             if(Math.abs(lp.leftMargin) > rightLayout.getLayoutParams().width/2){  
  226.                 new SlideMenu().execute(rightLayout.getLayoutParams().width -   
  227.                         Math.abs(lp.leftMargin), -SPEED);  
  228.             }else{  
  229.                 new SlideMenu().execute(Math.abs(lp.leftMargin), SPEED);  
  230.             }  
  231.         }else if(lp.leftMargin > 0){  
  232.             /* 
  233.              *  右移大于左导航宽度一半,则自动展开,否则自动缩回去 
  234.              */  
  235.             if(Math.abs(lp.leftMargin) > leftLayout.getLayoutParams().width/2){  
  236.                 new SlideMenu().execute(leftLayout.getLayoutParams().width -   
  237.                         Math.abs(lp.leftMargin), SPEED);  
  238.             }else{  
  239.                 new SlideMenu().execute(Math.abs(lp.leftMargin), -SPEED);  
  240.             }  
  241.         }  
  242.     }  
  243.   
  244.     / ListView.onItemClick ///  
  245.     @Override  
  246.     public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {  
  247.         Toast.makeText(this, title[arg2], Toast.LENGTH_SHORT).show();  
  248.     }  
  249.       
  250.     // onTouch ///  
  251.     @Override  
  252.     public boolean onTouch(View v, MotionEvent event) {  
  253.         mClickedView = v;  
  254.           
  255.         if(MotionEvent.ACTION_UP == event.getAction() && bIsScrolling){  
  256.             onRelease();  
  257.         }  
  258.           
  259.         return mGestureDetector.onTouchEvent(event);  
  260.     }  
  261.       
  262.     /// GestureDetector Override Begin ///  
  263.     @Override  
  264.     public boolean onDown(MotionEvent e) {  
  265.           
  266.         bIsScrolling = false;  
  267.         mScroll = 0;  
  268.         iLimited = 0;  
  269.         RelativeLayout.LayoutParams lp =   
  270.             (LayoutParams) mainLayout.getLayoutParams();  
  271.         if(lp.leftMargin > 0){  
  272.             iLimited = 1;  
  273.         }else if(lp.leftMargin < 0){  
  274.             iLimited = -1;  
  275.         }  
  276.           
  277.         return true;  
  278.     }  
  279.   
  280.     @Override  
  281.     public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2,  
  282.             float arg3) {  
  283.         return false;  
  284.     }  
  285.   
  286.     @Override  
  287.     public void onLongPress(MotionEvent e) {  
  288.           
  289.     }  
  290.   
  291.     @Override  
  292.     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,  
  293.             float distanceY) {  
  294.         onScroll(distanceX);  
  295.         return false;  
  296.     }  
  297.   
  298.     @Override  
  299.     public void onShowPress(MotionEvent arg0) {  
  300.           
  301.     }  
  302.   
  303.     @Override  
  304.     public boolean onSingleTapUp(MotionEvent e) {  
  305.         /* 
  306.          *  正常情况下,mainLayout的leftMargin为0, 
  307.          *  当左/右菜单为打开中,此时就不为0,需要判断 
  308.          */  
  309.         if(mClickedView != null){  
  310.             RelativeLayout.LayoutParams lp =   
  311.                 (LayoutParams) mainLayout.getLayoutParams();  
  312.               
  313.             if(mClickedView == ivMore){  
  314.                 Log.d(TAG, "[onSingleTapUp] ivMore clicked! leftMargin = "   
  315.                         + lp.leftMargin);  
  316.                   
  317.                 if(lp.leftMargin == 0){  
  318.                     new SlideMenu().execute(  
  319.                             leftLayout.getLayoutParams().width, SPEED);  
  320.                 }else{  
  321.                     new SlideMenu().execute(  
  322.                             leftLayout.getLayoutParams().width, -SPEED);  
  323.                 }  
  324.             }else if(mClickedView == ivSettings){  
  325.                 Log.d(TAG, "[onSingleTapUp] ivSettings clicked! leftMargin = "   
  326.                         + lp.leftMargin);  
  327.                   
  328.                 if(lp.leftMargin == 0){  
  329.                     new SlideMenu().execute(  
  330.                             rightLayout.getLayoutParams().width, -SPEED);  
  331.                 }else{  
  332.                     new SlideMenu().execute(  
  333.                             rightLayout.getLayoutParams().width, SPEED);  
  334.                 }  
  335.             }else if(mClickedView == mainLayout){  
  336.                 Log.d(TAG, "[onSingleTapUp] mainLayout clicked!");  
  337.             }  
  338.         }  
  339.         return true;  
  340.     }  
  341.     /// GestureDetector Override End ///  
  342.       
  343.     /** 
  344.      *  
  345.      * @author cheng.yang 
  346.      * 
  347.      *  左、右菜单滑出 
  348.      * 
  349.      *  params[0]: 滑动距离 
  350.      *  params[1]: 滑动速度,带方向 
  351.      */  
  352.     public class SlideMenu extends AsyncTask<Integer, Integer, Void>{  
  353.         @Override  
  354.         protected Void doInBackground(Integer... params) {  
  355.             if(params.length != 2){  
  356.                 Log.e(TAG, "error, params must have 2!");  
  357.             }  
  358.   
  359.             int times = params[0] / Math.abs(params[1]);  
  360.             if(params[0] % Math.abs(params[1]) != 0){  
  361.                 times ++;  
  362.             }  
  363.               
  364.             for(int i = 0; i < times; i++){  
  365.                 this.publishProgress(params[0], params[1], i+1);  
  366.             }  
  367.               
  368.             return null;  
  369.         }  
  370.   
  371.         @Override  
  372.         protected void onProgressUpdate(Integer... values) {  
  373.             if(values.length != 3){  
  374.                 Log.e(TAG, "error, values must have 3!");  
  375.             }  
  376.   
  377.             int distance = Math.abs(values[1]) * values[2];  
  378.             int delta = values[0] - distance;  
  379.   
  380.             int leftMargin = 0;  
  381.             if(values[1] < 0){ // 左移  
  382.                 leftMargin = (delta > 0 ? values[1] :   
  383.                     -(Math.abs(values[1]) - Math.abs(delta)));  
  384.             }else{  
  385.                 leftMargin = (delta > 0 ? values[1] :   
  386.                     (Math.abs(values[1]) - Math.abs(delta)));  
  387.             }  
  388.               
  389.             rollLayout(leftMargin);  
  390.         }  
  391.     }  
  392. }  

总结:

本文左,右滑出菜单的原理,就是用到了leftMargin和rightMargin两个属性,并配合几个GestureDetector来完全手势判断的逻辑处理,其中,自定义的那个控件布局LayoutRelative,可以用在任何子视图中需要处理上下,左右滚动冲突的地方,良好的解决了冲突问题。

 

完整的代码下载地址:

希望大家多来聊聊,同时,大家可以在我的基础上,实现onFling的方法,欢迎交流。

转载地址:https://blog.csdn.net/thinkinwm/article/details/9326855 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:android 定制个性按钮 控件
下一篇:Android tutorial: How to make a basic splash screen

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2024年04月03日 23时02分55秒