实现左侧滑动菜单(二)

 

Android仿人人客户端(v5.7.1)——采用RelativeLayout做父容器,实现左侧滑动菜单(二)

分类: 仿人人Android客户端(v5.7.1)   2240人阅读  评论(0)  收藏  举报

转载请标明出处:http://blog.csdn.net/android_ls/article/details/8758943

        上一篇在Android仿人人客户端(v5.7.1)——应用主界面之滑动效果(一)中,滑动式菜单的初步效果已实现,这篇继续完善和优化。打个比方:就拿盖房子来说,上一篇完成的只是毛胚房,这篇要做的就是对毛胚房进行精美装修,之后才可以使用。好了,进入正题,假设ViewGroup中有两个子View A和B,B处于A上面,两个子View是叠在一起的。默认显示的是B,并占据着整个手机屏幕,A是看不见的。为了能看见A,并且可以操作,我们需要把B视图(子View)移动一定单位,当A、B均可见并都可以接受事件。此时B视图可见部分就是手柄的宽度。

存在的问题:

       1、当长按B视图的可见部分(手柄),会发现会触发A视图的事件,这显然是不行的。用户明明在B视图上操作,响应的却是A视图中的子View。

       2、当在B视图的可见部分(手柄)上,用手指向右滑动时,发现B是还可以向右滑动。

       3、当B视图占据着整个手机屏幕时,在B视图的任何区域水平滑动都可以让B向右滑动,也就说触控区域太大,会引起误操作。

       4、手指水平滑动,响应很迟钝,灵敏度问题。

解决办法:

       添加触摸事件分发处理回调方法dispatchTouchEvent(MotionEvent ev),其返回值决定事件的分发给谁处理。当用户手指在屏幕上按下后,阅读下面代码片段:

[java]  view plain copy
  1. case MotionEvent.ACTION_DOWN:  
  2.      Log.i(TAG, "dispatchTouchEvent():  ACTION_DOWN");  
  3.        
  4.      mFinished = mScroller.isFinished();  
  5.      if(mFinished){  
  6.          int x = (int) ev.getX();  
  7.          int width = getWidth();  
  8.            
  9.          if(mPanelInvisible)// 左侧面板可见  
  10.          {  
  11.              if(x > (width - mHandlebarWidth)){ // 当前手指按下的坐标x值 ,是在手柄宽度范围内  
  12.                  isClick = true;  
  13.                  mAllowScroll = true;  
  14.                  return true;  
  15.              } else {  
  16.                  isClick = false;  
  17.                  mAllowScroll = false;  
  18.              }  
  19.          } else { // 左侧面板不可见  
  20.              if(x < mHandlebarWidth ){ // 当前手指按下的坐标x值 < 手柄宽度 (也就是说在手柄宽度范围内,是可以相应用户的向右滑动手势)  
  21.                  mAllowScroll = true;  
  22.              }else{  
  23.                  mAllowScroll = false;  
  24.              }  
  25.          }  
  26.            
  27.      } else {  
  28.          // 当前正在滚动子View,其它的事不响应  
  29.          return false;  
  30.      }  
  31.        
  32. break;  

         当前手指按下的坐标x值 ,是在手柄宽度范围内,改变标识值,返回true。表示onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)中的MotionEvent.ACTION_DOWN事件不再处理。

       上一个Action down事件处理完后,接下来响应dispatchTouchEvent(MotionEvent ev)的MotionEvent.ACTION_MOVE,代码如下:

[java]  view plain copy
  1. case MotionEvent.ACTION_MOVE:  
  2.           Log.i(TAG, "dispatchTouchEvent():  ACTION_MOVE");  
  3.           int margin = getWidth() - (int) ev.getX();  
  4.           if (margin < mHandlebarWidth && mAllowScroll) {  
  5.                 
  6.               Log.e(TAG, "dispatchTouchEvent ACTION_MOVE margin = " + margin + "\t mHandlebarWidth = " + mHandlebarWidth);  
  7.               return true;  
  8.           }  
  9.             
  10.           break;  

        在MotionEvent.ACTION_MOVE代码块中,发现条件是满足的,返回true。表示和MotionEvent.ACTION_DOWN的一样,在后续的回调方法内Action move将不再处理。最后看当手指抬起,处理的代码如下:

[java]  view plain copy
  1. case MotionEvent.ACTION_UP:  
  2.           Log.i(TAG, "dispatchTouchEvent():  ACTION_UP");  
  3.             
  4.           if (isClick && mPanelInvisible && mAllowScroll) {  
  5.               isClick = false;  
  6.               mPanelInvisible = false;  
  7.                 
  8.               int scrollX = getChildAt(1).getScrollX();  
  9.               mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);  
  10.               invalidate();  
  11.                 
  12.               return true;  
  13.           }  
  14.             
  15.           break;  


      上面的代码片段含义:当用户手指在屏幕上按下,当前ViewGroup中没有子View正在滚动,左侧面板处于可见,手指按下的坐标X值是在手柄(B视图的当前可见部分)的宽度范围内,则down事件到此处理结束。在move代码块里同样判断用户当前手指按下的坐标X值是在手柄宽度范围内的,则move事件也到此结束。在手指抬起后,up事件里判断在down是事件里设置的标识,发现条件都满足,响应用户在手柄上的单击事件请求。也就是说父View把用户的请求已响应了,不用传递到子View。子View也不知道发生过这件事。(大概意思就是这样)


        触控区域太大的问题,每次都判断用户手指按下的坐标x值,是否在手柄宽度范围内(不管左侧面板是否可见)。灵敏度的问题,其实就是响应滚动子View的临界值的大小问题,值越小灵敏都越高。

ScrollerContainer类,修改后的代码如下:

[java]  view plain copy
  1. package com.everyone.android.widget;  
  2.   
  3. import android.content.Context;  
  4. import android.util.Log;  
  5. import android.util.TypedValue;  
  6. import android.view.MotionEvent;  
  7. import android.view.VelocityTracker;  
  8. import android.view.ViewConfiguration;  
  9. import android.widget.RelativeLayout;  
  10. import android.widget.Scroller;  
  11.   
  12. /** 
  13.  * 功能描述:手指在屏幕上左右滑动时,该类的实例负责让其子View根据用户的手势左右偏移(滚动) 
  14.  *  
  15.  * @author android_ls 
  16.  */  
  17. public class ScrollerContainer extends RelativeLayout {  
  18.   
  19.     private static final String TAG = "ScrollerContainer";  
  20.   
  21.     private Scroller mScroller;  
  22.   
  23.     private VelocityTracker mVelocityTracker;  
  24.   
  25.     /** 
  26.      * 手柄(手把)的宽度 
  27.      */  
  28.     private int mHandlebarWidth;  
  29.   
  30.     /** 
  31.      * 在偏移过程中,动画持续的时间 
  32.      */  
  33.     private static final int ANIMATION_DURATION_TIME = 300;  
  34.       
  35.     /** 
  36.      * 记录当前的滑动结束后的状态,左侧面板是否可见 
  37.      * true  向右滑动(左侧面板处于可见) 
  38.      * false 向左滑动(左侧面板处于不可见) 
  39.      */  
  40.     private boolean mPanelInvisible;  
  41.       
  42.     /** 
  43.      * 是否已滑动结束 
  44.      */  
  45.     private boolean mFinished;  
  46.       
  47.     /** 
  48.      * 是否允许滚动 
  49.      * 满足的条件: 
  50.      *     左侧面板可见,当前手指按下的坐标x值 ,是在手柄宽度范围内; 
  51.      *     左侧面板不可见,当前手指按下的坐标x值 < 手柄宽度 
  52.      */  
  53.     private boolean mAllowScroll;  
  54.       
  55.     /** 
  56.      * 是否满足响应单击事件的条件 
  57.      * 满足的条件:左侧面板可见,当前手指按下的坐标x值 ,是在手柄宽度范围内 
  58.      */  
  59.     private boolean isClick;  
  60.       
  61.     public ScrollerContainer(Context context) {  
  62.         super(context);  
  63.   
  64.         mScroller = new Scroller(context);  
  65.         mHandlebarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics());  
  66.     }  
  67.       
  68.     @Override  
  69.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  70.         Log.e(TAG, "dispatchTouchEvent()");  
  71.           
  72.         switch (ev.getAction()) {  
  73.         case MotionEvent.ACTION_DOWN:  
  74.             Log.i(TAG, "dispatchTouchEvent():  ACTION_DOWN");  
  75.               
  76.             mFinished = mScroller.isFinished();  
  77.             if(mFinished){  
  78.                 int x = (int) ev.getX();  
  79.                 int width = getWidth();  
  80.                   
  81.                 if(mPanelInvisible)// 左侧面板可见  
  82.                 {  
  83.                     if(x > (width - mHandlebarWidth)){ // 当前手指按下的坐标x值 ,是在手柄宽度范围内  
  84.                         isClick = true;  
  85.                         mAllowScroll = true;  
  86.                         return true;  
  87.                     } else {  
  88.                         isClick = false;  
  89.                         mAllowScroll = false;  
  90.                     }  
  91.                 } else { // 左侧面板不可见  
  92.                     if(x < mHandlebarWidth ){ // 当前手指按下的坐标x值 < 手柄宽度 (也就是说在手柄宽度范围内,是可以相应用户的向右滑动手势)  
  93.                         mAllowScroll = true;  
  94.                     }else{  
  95.                         mAllowScroll = false;  
  96.                     }  
  97.                 }  
  98.                   
  99.             } else {  
  100.                 // 当前正在滚动子View,其它的事不响应  
  101.                 return false;  
  102.             }  
  103.               
  104.             break;  
  105.   
  106.         case MotionEvent.ACTION_MOVE:  
  107.             Log.i(TAG, "dispatchTouchEvent():  ACTION_MOVE");  
  108.             int margin = getWidth() - (int) ev.getX();  
  109.             if (margin < mHandlebarWidth && mAllowScroll) {  
  110.                   
  111.                 Log.e(TAG, "dispatchTouchEvent ACTION_MOVE margin = " + margin + "\t mHandlebarWidth = " + mHandlebarWidth);  
  112.                 return true;  
  113.             }  
  114.               
  115.             break;  
  116.         case MotionEvent.ACTION_UP:  
  117.             Log.i(TAG, "dispatchTouchEvent():  ACTION_UP");  
  118.               
  119.             if (isClick && mPanelInvisible && mAllowScroll) {  
  120.                 isClick = false;  
  121.                 mPanelInvisible = false;  
  122.                   
  123.                 int scrollX = getChildAt(1).getScrollX();  
  124.                 mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);  
  125.                 invalidate();  
  126.                   
  127.                 return true;  
  128.             }  
  129.               
  130.             break;  
  131.         case MotionEvent.ACTION_CANCEL:  
  132.             Log.i(TAG, "dispatchTouchEvent():  ACTION_CANCEL");  
  133.             break;  
  134.         default:  
  135.             break;  
  136.         }  
  137.           
  138.         return super.dispatchTouchEvent(ev);  
  139.     }  
  140.       
  141.     @Override  
  142.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  143.         Log.e(TAG, "onInterceptTouchEvent()");  
  144.           
  145.         switch (ev.getAction()) {  
  146.         case MotionEvent.ACTION_DOWN:  
  147.             Log.i(TAG, "onInterceptTouchEvent():  ACTION_DOWN");  
  148.             mFinished = mScroller.isFinished();  
  149.             if(!mFinished){  
  150.                 return false;  
  151.             }  
  152.               
  153.             break;  
  154.         case MotionEvent.ACTION_MOVE:  
  155.             Log.i(TAG, "onInterceptTouchEvent():  ACTION_MOVE");  
  156.               
  157.             mVelocityTracker = VelocityTracker.obtain();  
  158.             mVelocityTracker.addMovement(ev);  
  159.               
  160.             // 一秒时间内移动了多少个像素  
  161.             mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity());  
  162.             float velocityValue = Math.abs(mVelocityTracker.getXVelocity()) ;  
  163.             Log.d(TAG, "onInterceptTouchEvent():  mVelocityValue = " + velocityValue);  
  164.               
  165.             if (velocityValue > 300 && mAllowScroll) {  
  166.                 return true;  
  167.             }  
  168.               
  169.             break;  
  170.         case MotionEvent.ACTION_UP:  
  171.             Log.i(TAG, "onInterceptTouchEvent():  ACTION_UP");  
  172.               
  173.             if (mVelocityTracker != null) {  
  174.                 mVelocityTracker.recycle();  
  175.                 mVelocityTracker = null;  
  176.             }  
  177.               
  178.             break;  
  179.         case MotionEvent.ACTION_CANCEL:  
  180.             Log.i(TAG, "onInterceptTouchEvent():  ACTION_CANCEL");  
  181.             break;  
  182.         default:  
  183.             break;  
  184.         }  
  185.           
  186.         return super.onInterceptTouchEvent(ev);  
  187.     }  
  188.   
  189.     @Override  
  190.     public boolean onTouchEvent(MotionEvent event) {  
  191.         Log.e(TAG, "onTouchEvent()");  
  192.   
  193.         float x = event.getX();  
  194.           
  195.         switch (event.getAction()) {  
  196.         case MotionEvent.ACTION_DOWN:  
  197.             Log.i(TAG, "onTouchEvent():  ACTION_DOWN");  
  198.             mFinished = mScroller.isFinished();  
  199.             if(!mFinished){  
  200.                 return false;  
  201.             }  
  202.             break;  
  203.   
  204.         case MotionEvent.ACTION_MOVE:  
  205.             Log.i(TAG, "onTouchEvent():  ACTION_MOVE");  
  206.             getChildAt(1).scrollTo(-(int)x, 0);  
  207.             break;  
  208.   
  209.         case MotionEvent.ACTION_UP:  
  210.             Log.i(TAG, "onTouchEvent():  ACTION_UP");  
  211.               
  212.             if(!mAllowScroll){  
  213.                 break;  
  214.             }  
  215.               
  216.            float width = getWidth();  
  217.            // 响应滚动子View的临界值,若觉得响应过于灵敏,可以将只改大些。  
  218.            // 比如:criticalWidth = width / 3或criticalWidth = width / 2,看情况而定,呵呵。  
  219.            float criticalWidth = width / 5;  
  220.              
  221.            Log.i(TAG, "onTouchEvent():  ACTION_UP x = " + x + "\t criticalWidth = " + criticalWidth);  
  222.              
  223.            int scrollX = getChildAt(1).getScrollX();  
  224.              
  225.            if ( x < criticalWidth) {  
  226.                Log.i(TAG, "onTouchEvent():  ACTION_UP 向左滑动");  
  227.                  
  228.                 mPanelInvisible = false;  
  229.                  
  230.                 mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);  
  231.                 invalidate();  
  232.             } else if ( x > criticalWidth){  
  233.                 Log.i(TAG, "onTouchEvent():  ACTION_UP 向右滑动");  
  234.                 
  235.                 mPanelInvisible = true;  
  236.                   
  237.                 int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);  
  238.                 mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);  
  239.                 invalidate();  
  240.             }  
  241.               
  242.             break;  
  243.         case MotionEvent.ACTION_CANCEL:  
  244.             Log.i(TAG, "onTouchEvent():  ACTION_CANCEL");  
  245.             break;  
  246.         default:  
  247.             break;  
  248.         }  
  249.           
  250.         return super.onTouchEvent(event);  
  251.     }  
  252.   
  253.     @Override  
  254.     public void computeScroll() {  
  255.         // super.computeScroll();  
  256.           
  257.         if(mScroller.computeScrollOffset()){  
  258.             this.getChildAt(1).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
  259.             this.postInvalidate();  
  260.         }  
  261.     }  
  262.   
  263.     /** 
  264.      * 向右滑动View,让左侧操作面饭可见 
  265.      */  
  266.     public void slideToRight() {  
  267.         mFinished = mScroller.isFinished();  
  268.         if(mFinished && !mPanelInvisible){  
  269.             mPanelInvisible = true;  
  270.               
  271.             float width = getWidth();  
  272.             int scrollX = getChildAt(1).getScrollX();  
  273.             int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);  
  274.               
  275.             mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);  
  276.             invalidate();  
  277.         }  
  278.     }  
  279.       
  280.     /** 
  281.      * View滑动事件监听器 
  282.      * @author android_ls 
  283.      */  
  284.     public interface OnSlideListener {  
  285.         /** 
  286.          * 向左滑动子View 
  287.          */  
  288.         public abstract void toLeft();  
  289.           
  290.         /** 
  291.          * 向右滑动子View 
  292.          */  
  293.         public abstract void toRight();  
  294.     }  
  295.       
  296. }  

主应用界面源码:

[java]  view plain copy
  1. package com.everyone.android.ui;  
  2.   
  3. import android.os.Bundle;  
  4. import android.view.ViewGroup.LayoutParams;  
  5.   
  6. import com.everyone.android.AppBaseActivity;  
  7. import com.everyone.android.widget.FreshNewsLayout;  
  8. import com.everyone.android.widget.LeftPanelLayout;  
  9. import com.everyone.android.widget.ScrollerContainer;  
  10. import com.everyone.android.widget.ScrollerContainer.OnSlideListener;  
  11.   
  12. /** 
  13.  * 功能描述:应用主界面 
  14.  * @author android_ls 
  15.  * 
  16.  */  
  17. public class EveryoneActivity extends AppBaseActivity implements OnSlideListener {  
  18.   
  19.     /** 
  20.      * 滚动(滑动)容器 
  21.      */  
  22.     private ScrollerContainer mSlideContainer;  
  23.   
  24.     /** 
  25.      * 左侧面板 
  26.      */  
  27.     private LeftPanelLayout mLeftPanelLayout;  
  28.   
  29.     /** 
  30.      * 新鲜事 
  31.      */  
  32.     private FreshNewsLayout mFreshNewsLayout;  
  33.   
  34.     @Override  
  35.     public void onCreate(Bundle savedInstanceState) {  
  36.         super.onCreate(savedInstanceState);  
  37.         setContentView(mSlideContainer);  
  38.   
  39.     }  
  40.   
  41.     @Override  
  42.     protected int getLayoutId() {  
  43.         return 0;  
  44.     }  
  45.   
  46.     @Override  
  47.     protected void setupView() {  
  48.         mSlideContainer = new ScrollerContainer(mContext);  
  49.   
  50.         mLeftPanelLayout = new LeftPanelLayout(mContext);  
  51.         mFreshNewsLayout = new FreshNewsLayout(mContext);  
  52.         mFreshNewsLayout.setOnSlideListener(this);  
  53.   
  54.         LayoutParams layoutParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);  
  55.         mSlideContainer.addView(mLeftPanelLayout, layoutParams);  
  56.         mSlideContainer.addView(mFreshNewsLayout, layoutParams);  
  57.   
  58.     }  
  59.   
  60.     @Override  
  61.     protected void initialized() {  
  62.         // TODO Auto-generated method stub  
  63.   
  64.     }  
  65.   
  66.     @Override  
  67.     public void toLeft() {  
  68.         // TODO Auto-generated method stub  
  69.   
  70.     }  
  71.   
  72.     @Override  
  73.     public void toRight() {  
  74.         mSlideContainer.slideToRight();  
  75.     }  
  76.   
  77. }  

AppBaseActivity类的修改部分,在onCreate里添加的处理:

[java]  view plain copy
  1.         int layoutId = getLayoutId();  
  2.         if(layoutId != 0){  
  3.             setContentView(getLayoutId());  
  4.         }  

有关滑动菜单的到这里就完了,后面在使用过程中遇到什么问题,再处理。

看下效果图,还和上一篇一样,静态的图片看不出来优化后的效果,不过还是上传几张,有图有真相。

向右滑动或点击顶部箭头后

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值