Android中实现滑动(拖动)的几种方法,玩转SlideMenu

时间:2021-05-05 19:24:50


潜水已久,看了CSDN上很多大牛的博客,学了不少东西,很钦佩这种无私奉献共享的精神。

自己平时有些笔记的习惯,虽然都是一些很基础的东西,最近安定了下来,整理一下复习复习,毕竟自己一个马大哈来着。。。各种健忘


滑动一个View,改变其当前所处的位置,这个在我们APP开发中实现常用的,例如侧滑菜单,购物车的拖动等等,所以学习一下是非常有必要的


CSDN客户端上也有一个侧滑的效果

Android中实现滑动(拖动)的几种方法,玩转SlideMenu




在学习如何实现滑动之前,首先要对Android中窗口坐标体系有一个大概的了解


Android坐标系:

这个没什么好说的,已屏幕最左上角的顶点作为Android坐标系的原点,往右是X轴的正方向,往下是Y轴的正方向


视图坐标系:

描述了子View与父容器之前的位置关系,在这里,原点就不再是屏幕的最上角了,而是父视图左上角为原点

                                 Android中实现滑动(拖动)的几种方法,玩转SlideMenu



MotionEvent对象:封装了触控事件中一些事件常量和常用的坐标方法


事件常量:

ACTION_DOWN        单点按下动作

ACTION_UP               单点离开动作

ACTION_MOVE          触摸移动动作

ACTION_ACTION_OUTSIDE      触摸动作超出边界

ACTION_POINTER_DOWN       多点触摸按下动作

ACTION_CANCEL     触摸动作取消

ACTION_POINTER_UP       多点离开动作


此外,还提供了很多方法来获取相应的坐标值,基本上就是通过这些坐标值来实现滑动的效果


为了防止遗忘也便于以后查询,画了个图来加深下记忆

Android中实现滑动(拖动)的几种方法,玩转SlideMenu

View提供获取坐标的方法:


getTop:View  顶部   到父容器    定边的距离

getLeft:View   左边   到父容器   左边的距离

getRight:View   右边  到父容器   左边的距离(将参考点想成父容器的左边,即父容器到该View右边的距离)

etBottom:View  底部  到父容器 顶边的距离

MotionEvent提供的方法:

getX:点击位置   到View   左边的距离(视图坐标)

getY:点击位置  到View    顶部的距离

getRawX:点击位置   到屏幕    左边的距离 (绝对坐标)

getRawY:点击位置   到屏幕    顶边的距离


实现移动:

实现移动的方法有很多,但是基本的原理都是一样的。当触摸View的时候,记录下触摸点的坐标,手指移动的时候,再获取一次坐标,这时将移动时获取到的坐标与前一次坐标做比较,即可得到偏移量,通过这个便宜量修改View的坐标即可实现移动。

下面通过代码来做一个简单的实现

继承View,重写onTouchEvent方法

[java] view plain copyprint?
  1. int downX = 0;  
  2. int downY = 0;  
  3. @Override  
  4. public boolean onTouchEvent(MotionEvent event) {  
  5.     int rawX = (int) event.getRawX();  
  6.     int rawY = (int) event.getRawY();  
  7.     switch (event.getAction()){  
  8.         case MotionEvent.ACTION_DOWN:  
  9.             //当触摸时,记录坐标  
  10.             downX = rawX;  
  11.             downY = rawY;  
  12.             break;  
  13.         case MotionEvent.ACTION_MOVE:  
  14.             //移动时,记录坐标,并计算出偏移量  
  15.             int offsetX = rawX-downX;  
  16.             int offsetY = rawY-downY;  
  17.             //在当前left  top  bottom  right上加上偏移量,得到的就是新的坐标  
  18.             layout(getLeft()+offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);  
  19.             downX = rawX;  
  20.             downY = rawY;  
  21.             break;  
  22.     }  
  23.     return true;  
  24. }  
Android中实现滑动(拖动)的几种方法,玩转SlideMenu
效果

Android中实现滑动(拖动)的几种方法,玩转SlideMenu

方法二:

使用LayoutParams

原理:LayoutParams封装了View的所有布局参数,可以通过改变他的leftManager和topManager来实现移动效果

[java] view plain copyprint?
  1. case MotionEvent.ACTION_MOVE:  
  2.   
  3.                int offsetX = rawX - downX;  
  4.                int offsetY = rawY - downY;  
  5.                ViewGroup.MarginLayoutParams layoutP = (ViewGroup.MarginLayoutParams)getLayoutParams();  
  6.                layoutP.leftMargin = getLeft()+offsetX;  
  7.                layoutP.topMargin = getTop()+offsetY;  
  8.                setLayoutParams(layoutP);  
  9.                downX = rawX;  
  10.                downY = rawY;  
  11.                break;  
Android中实现滑动(拖动)的几种方法,玩转SlideMenu

实现的效果是一样的

另外,谷歌还为我们提供了左右,上下移动的API封装,offsetLeftAndRight()和offsetTopAndBottom()


方法三:

ScrollBy和ScrollTo


     在View中使用:移动的就是View的内容,(如在Button中,移动的就是button中的text)

     在ViewGroup中,移动的将是所有的View(移动可见视图)


在View中使用的效果:

Android中实现滑动(拖动)的几种方法,玩转SlideMenu


           移动的其实就是视图的可视区域

   那么什么是可视区域呢?

           假如我们要移动的View布局是这样的

         

[html] view plain copyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical"  
  7.     >  
  8.     <com.czp.view.ScrollView  
  9.         android:layout_width="150dp"  
  10.         android:layout_height="150dp"  
  11.         android:background="#f00"  
  12.         >  
  13.   
  14.     </com.czp.view.ScrollView>  
  15. </LinearLayout>  
Android中实现滑动(拖动)的几种方法,玩转SlideMenu

  使用((View)getParent()).scrollBy(-offsetX,-offsetY)来实现移动,(先不管为什么这里要将计算出来的偏移量设置成负值)

              ScorllerTo(by)移动的是可视区域,而不是内容

           要知道,Android的坐标系是没有边界的,屏幕显示内容的框也是一个可视区域

Android中实现滑动(拖动)的几种方法,玩转SlideMenu


是不是挺形象的?到了这里也就能明白scrollBy(-offsetX,-offsetY)为什么要写负值了

本来我们View往右边移动的,偏移量应该是正的,但是scrollBy移动是的可视区域,但是可视区域

向左边移动的,View是往右边的,位置反向



Scroller实现:

前面介绍的几种方法,在完成移动的时候,都是瞬间完成的,看起来非常生硬,让人感觉很粗糙,应该给他加一点动画让他看起来平滑一下

Scroller就是来完成这项工作的。


使用Scroller的步骤

初始化

scroller = new Scrollr(context)


重写computeScroll方法,在使用startScoller后,回不断调用该方法进行重绘


使用StartScroll开发滑动

public void startScroll(int startX,int StartY,int dx,int dy)


参数

  startX 水平方向滚动的偏移值,以像素为单位。正值表明滚动将向左滚动

  startY 垂直方向滚动的偏移值,以像素为单位。正值表明滚动将向上滚动

  dx 水平方向滑动的距离,正值会使滚动向左滚动

  dy 垂直方向滑动的距离,正值会使滚动向上滚动

下面通过一例子理解一下


[java] view plain copyprint?
  1. @Override  
  2. public void computeScroll() {  
  3.     super.computeScroll();  
  4.     //判断移动是否执行完毕  
  5.     if(scroller.computeScrollOffset()){  
  6.         ((View)getParent()).scrollTo(scroller.getCurrX(), scroller.getCurrY());  
  7.     }  
  8. }  
  9.   
  10. int downX = 0;  
  11. int downY = 0;  
  12. @Override  
  13. public boolean onTouchEvent(MotionEvent event) {  
  14.     int rawX = (int) event.getRawX();  
  15.     int rawY = (int) event.getRawY();  
  16.     switch (event.getAction()){  
  17.         case MotionEvent.ACTION_DOWN:  
  18.             //当触摸时,记录坐标  
  19.             downX = rawX;  
  20.             downY = rawY;  
  21.             break;  
  22.         case MotionEvent.ACTION_MOVE:  
  23.             int offsetX = rawX - downX;  
  24.             int offsetY = rawY - downY;  
  25.             ((View)getParent()).scrollBy(-offsetX,-offsetY);  
  26.             downX = rawX;  
  27.             downY = rawY;  
  28.             break;  
  29.         case MotionEvent.ACTION_UP:  
  30.             scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), -scroller.getCurrX(), -scroller.getCurrY(), 400);  
  31.             invalidate();  
  32.             break;  
  33.     }  
  34.     return true;  
  35. }  
Android中实现滑动(拖动)的几种方法,玩转SlideMenu


Android中实现滑动(拖动)的几种方法,玩转SlideMenu




综合以上的知识,我们完成可以自己实现一个SlideMenu侧滑控件


代码相当简单,没有什么难点

[java] view plain copyprint?
  1. public class SlideMenu extends ViewGroup {  
  2.     private View mMainLayout, mMenuLayout;  
  3.     private Scroller scroller;  
  4.     private Context context;  
  5.     public SlideMenu(Context context) {  
  6.         super(context);  
  7.         this.context = context;  
  8.         init();  
  9.     }  
  10.   
  11.     public SlideMenu(Context context, AttributeSet attrs) {  
  12.         super(context, attrs);  
  13.         this.context = context;  
  14.         init();  
  15.     }  
  16.   
  17.     public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {  
  18.         super(context, attrs, defStyleAttr);  
  19.         this.context = context;  
  20.         init();  
  21.     }  
  22.   
  23.     @Override  
  24.     public void computeScroll() {  
  25.         super.computeScroll();  
  26.         //判断动画是否执行结束  
  27.         if(scroller.computeScrollOffset()){  
  28.             scrollTo(scroller.getCurrX(),0);  
  29.             invalidate();  
  30.         }  
  31.     }  
  32.   
  33.     public void init(){  
  34.         scroller = new Scroller(context);  
  35.     }  
  36.     /** 
  37.      * 当一级的子View加载完的时候调用 
  38.      */  
  39.     @Override  
  40.     protected void onFinishInflate() {  
  41.         super.onFinishInflate();  
  42.         mMenuLayout = getChildAt(0);  
  43.         mMainLayout = getChildAt(1);  
  44.     }  
  45.   
  46.     @Override  
  47.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  48.         mMenuLayout.measure(mMenuLayout.getLayoutParams().width,heightMeasureSpec);  
  49.         mMainLayout.measure(widthMeasureSpec,heightMeasureSpec);  
  50.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  51.     }  
  52.     @Override  
  53.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  54.         mMenuLayout.layout(-mMenuLayout.getLayoutParams().width,0,0,b);  
  55.         mMainLayout.layout(0,0,r,b);  
  56.     }  
  57.     int downX = 0;  
  58.     @Override  
  59.     public boolean onTouchEvent(MotionEvent event) {  
  60.         switch (event.getAction()){  
  61.             case MotionEvent.ACTION_DOWN:  
  62.                 downX = (int) event.getRawX();  
  63.                 break;  
  64.             case MotionEvent.ACTION_MOVE:  
  65.                 Log.e("DEMO","downX = "+downX  +"  MOVE == "+event.getRawX());  
  66.                 int offsetX = (int) (event.getRawX() - downX);  
  67.                 //当前滚动的位置加要滚动的距离,即得到目标位置  
  68.                 int scrollX = getScrollX()-offsetX;  
  69.                 Log.e("DEMO","scrollX == "+scrollX);  
  70.                 if(scrollX>=-mMenuLayout.getLayoutParams().width&&scrollX<=0){  
  71.                     scrollTo(scrollX,0);  
  72.                     downX = (int) event.getX();  
  73.                 }  
  74.                 break;  
  75.             case MotionEvent.ACTION_UP:  
  76.                 if(getScrollX()>-mMenuLayout.getLayoutParams().width/2){  
  77.                     closeMenu();  
  78.                 }else{  
  79.                     openMenu();  
  80.                 }  
  81.                 break;  
  82.         }  
  83.         return true;  
  84.     }  
  85.     /** 
  86.      * 关闭菜单 
  87.      */  
  88.     public void closeMenu(){  
  89. //        scrollTo(0,0);  
  90.         scroller.startScroll(getScrollX(),0,0-getScrollX(),0,400);  
  91.         invalidate();  
  92.     }  
  93.     /** 
  94.      * 打开菜单 
  95.      */  
  96.     public void openMenu(){  
  97. //        scrollTo(-mMenuLayout.getLayoutParams().width,0);  
  98.         scroller.startScroll(getScrollX(), 0, -mMenuLayout.getLayoutParams().width-getScrollX(), 0400);  
  99.         invalidate();  
  100.     }  
  101. }  
Android中实现滑动(拖动)的几种方法,玩转SlideMenu


布局文件


[html] view plain copyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"  
  4.     android:layout_height="match_parent" tools:context=".MainActivity">  
  5.   
  6.     <com.example.administrator.myapplication.SlideMenu  
  7.         android:layout_width="fill_parent"  
  8.         android:layout_height="fill_parent"  
  9.         >  
  10.         <LinearLayout  
  11.             android:layout_width="150dp"  
  12.             android:background="#f00"  
  13.             android:layout_height="fill_parent">  
  14.         </LinearLayout>  
  15.   
  16.         <LinearLayout  
  17.             android:layout_width="fill_parent"  
  18.             android:background="#88dc73"  
  19.             android:layout_height="fill_parent">  
  20.         </LinearLayout>  
  21.     </com.example.administrator.myapplication.SlideMenu>  
  22.   
  23. </RelativeLayout>  
Android中实现滑动(拖动)的几种方法,玩转SlideMenu



Android中实现滑动(拖动)的几种方法,玩转SlideMenu