zoukankan      html  css  js  c++  java
  • Android 一步一步教你使用ViewDragHelper

    在自定义viewgroup的时候 要重写onInterceptTouchEventonTouchEvent 这2个方法 是非常麻烦的事情,好在谷歌后来

    推出了ViewDragHelper这个类。可以极大方便我们自定义viewgroup.

    先看一个简单效果 一个layout里有2个图片 其中有一个可以滑动 一个不能滑

    这个效果其实还蛮简单的(原谅我让臭脚不能动 让BABY动)

    布局文件:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     android:orientation="vertical">
     6 
     7     <com.example.administrator.viewdragertestapp.DragLayout
     8         android:layout_width="match_parent"
     9         android:layout_height="match_parent"
    10         android:orientation="vertical">
    11 
    12         <ImageView
    13             android:id="@+id/iv1"
    14             android:layout_width="wrap_content"
    15             android:layout_height="wrap_content"
    16             android:layout_gravity="center_horizontal"
    17             android:src="@drawable/a1"></ImageView>
    18 
    19         <ImageView
    20             android:id="@+id/iv2"
    21             android:layout_width="wrap_content"
    22             android:layout_height="wrap_content"
    23             android:layout_gravity="center_horizontal"
    24             android:src="@drawable/a2"></ImageView>
    25 
    26 
    27     </com.example.administrator.viewdragertestapp.DragLayout>
    28 
    29 </LinearLayout>

    然后我们看一下自定义的layout 如何实现2个子view 一个可以滑动 一个不能滑动的

     1 package com.example.administrator.viewdragertestapp;
     2 
     3 import android.content.Context;
     4 import android.support.v4.widget.ViewDragHelper;
     5 import android.util.AttributeSet;
     6 import android.view.MotionEvent;
     7 import android.view.View;
     8 import android.widget.ImageView;
     9 import android.widget.LinearLayout;
    10 import android.widget.TextView;
    11 
    12 /**
    13  * Created by Administrator on 2015/8/12.
    14  */
    15 public class DragLayout extends LinearLayout {
    16 
    17     private ViewDragHelper mDragger;
    18 
    19     private ViewDragHelper.Callback callback;
    20 
    21     private ImageView iv1;
    22     private ImageView iv2;
    23 
    24     @Override
    25     protected void onFinishInflate() {
    26         iv1 = (ImageView) this.findViewById(R.id.iv1);
    27         iv2 = (ImageView) this.findViewById(R.id.iv2);
    28         super.onFinishInflate();
    29 
    30     }
    31 
    32     public DragLayout(Context context) {
    33         super(context);
    34 
    35     }
    36 
    37     public DragLayout(Context context, AttributeSet attrs) {
    38         super(context, attrs);
    39         callback = new DraggerCallBack();
    40         //第二个参数就是滑动灵敏度的意思 可以随意设置
    41         mDragger = ViewDragHelper.create(this, 1.0f, callback);
    42     }
    43 
    44     class DraggerCallBack extends ViewDragHelper.Callback {
    45 
    46         //这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动
    47         @Override
    48         public boolean tryCaptureView(View child, int pointerId) {
    49             if (child == iv2) {
    50                 return false;
    51             }
    52             return true;
    53         }
    54 
    55         @Override
    56         public int clampViewPositionHorizontal(View child, int left, int dx) {
    57             return left;
    58         }
    59 
    60         @Override
    61         public int clampViewPositionVertical(View child, int top, int dy) {
    62             return top;
    63         }
    64     }
    65 
    66 
    67     @Override
    68     public boolean onInterceptTouchEvent(MotionEvent ev) {
    69         //决定是否拦截当前事件
    70         return mDragger.shouldInterceptTouchEvent(ev);
    71     }
    72 
    73     @Override
    74     public boolean onTouchEvent(MotionEvent event) {
    75         //处理事件
    76         mDragger.processTouchEvent(event);
    77         return true;
    78     }
    79 
    80 
    81 }

    然后再完善一下这个layout,刚才滑动的时候我们的view 出了屏幕的边界很不美观 现在我们修改2个函数 让滑动的范围

    在这个屏幕之内(准确的说是在这个layout之内,因为我们的布局文件layout充满了屏幕 所以看上去是在屏幕内)

     1  //这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。
     2         // 我们要让view滑动的范围在我们的layout之内
     3         //实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。
     4         //如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围
     5         //除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的.
     6 
     7         @Override
     8         public int clampViewPositionHorizontal(View child, int left, int dx) {
     9             //取得左边界的坐标
    10             final int leftBound = getPaddingLeft();
    11             //取得右边界的坐标
    12             final int rightBound = getWidth() - child.getWidth() - leftBound;
    13             //这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left
    14             //如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值
    15             //如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值
    16             return Math.min(Math.max(left, leftBound), rightBound);
    17         }
    18 
    19         //纵向的注释就不写了 自己体会
    20         @Override
    21         public int clampViewPositionVertical(View child, int top, int dy) {
    22             final int topBound = getPaddingTop();
    23             final int bottomBound = getHeight() - child.getHeight() - topBound;
    24             return Math.min(Math.max(top, topBound), bottomBound);
    25         }

    我们看下效果

    然后我们可以再加上一个回弹的效果,就是你把babay拉倒一个位置 然后松手他会自动回弹到初始位置

    其实思路很简单 就是你松手的时候 回到初始的坐标位置即可。

      1 package com.example.administrator.viewdragertestapp;
      2 
      3 import android.content.Context;
      4 import android.graphics.Point;
      5 import android.support.v4.widget.ViewDragHelper;
      6 import android.util.AttributeSet;
      7 import android.view.MotionEvent;
      8 import android.view.View;
      9 import android.widget.ImageView;
     10 import android.widget.LinearLayout;
     11 import android.widget.TextView;
     12 
     13 /**
     14  * Created by Administrator on 2015/8/12.
     15  */
     16 public class DragLayout extends LinearLayout {
     17 
     18     private ViewDragHelper mDragger;
     19 
     20     private ViewDragHelper.Callback callback;
     21 
     22     private ImageView iv1;
     23     private ImageView iv2;
     24 
     25     private Point initPointPosition = new Point();
     26 
     27     @Override
     28     protected void onFinishInflate() {
     29         iv1 = (ImageView) this.findViewById(R.id.iv1);
     30         iv2 = (ImageView) this.findViewById(R.id.iv2);
     31         super.onFinishInflate();
     32 
     33     }
     34 
     35     public DragLayout(Context context) {
     36         super(context);
     37 
     38     }
     39 
     40     public DragLayout(Context context, AttributeSet attrs) {
     41         super(context, attrs);
     42         callback = new DraggerCallBack();
     43         //第二个参数就是滑动灵敏度的意思 可以随意设置
     44         mDragger = ViewDragHelper.create(this, 1.0f, callback);
     45     }
     46 
     47     class DraggerCallBack extends ViewDragHelper.Callback {
     48 
     49         //这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动
     50         @Override
     51         public boolean tryCaptureView(View child, int pointerId) {
     52             if (child == iv2) {
     53                 return false;
     54             }
     55             return true;
     56         }
     57 
     58 
     59         //这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。
     60         // 我们要让view滑动的范围在我们的layout之内
     61         //实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。
     62         //如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围
     63         //除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的.
     64 
     65         @Override
     66         public int clampViewPositionHorizontal(View child, int left, int dx) {
     67             //取得左边界的坐标
     68             final int leftBound = getPaddingLeft();
     69             //取得右边界的坐标
     70             final int rightBound = getWidth() - child.getWidth() - leftBound;
     71             //这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left
     72             //如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值
     73             //如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值
     74             return Math.min(Math.max(left, leftBound), rightBound);
     75         }
     76 
     77         //纵向的注释就不写了 自己体会
     78         @Override
     79         public int clampViewPositionVertical(View child, int top, int dy) {
     80             final int topBound = getPaddingTop();
     81             final int bottomBound = getHeight() - child.getHeight() - topBound;
     82             return Math.min(Math.max(top, topBound), bottomBound);
     83         }
     84 
     85         @Override
     86         public void onViewReleased(View releasedChild, float xvel, float yvel) {
     87             //松手的时候 判断如果是这个view 就让他回到起始位置
     88             if (releasedChild == iv1) {
     89                 //这边代码你跟进去去看会发现最终调用的是startScroll这个方法 所以我们就明白还要在computeScroll方法里刷新
     90                 mDragger.settleCapturedViewAt(initPointPosition.x, initPointPosition.y);
     91                 invalidate();
     92             }
     93         }
     94     }
     95 
     96     @Override
     97     public void computeScroll() {
     98         if (mDragger.continueSettling(true)) {
     99             invalidate();
    100         }
    101     }
    102 
    103     @Override
    104     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    105         super.onLayout(changed, l, t, r, b);
    106         //布局完成的时候就记录一下位置
    107         initPointPosition.x = iv1.getLeft();
    108         initPointPosition.y = iv1.getTop();
    109     }
    110 
    111     @Override
    112     public boolean onInterceptTouchEvent(MotionEvent ev) {
    113         //决定是否拦截当前事件
    114         return mDragger.shouldInterceptTouchEvent(ev);
    115     }
    116 
    117     @Override
    118     public boolean onTouchEvent(MotionEvent event) {
    119         //处理事件
    120         mDragger.processTouchEvent(event);
    121         return true;
    122     }
    123 
    124 
    125 }

    看下效果:

    到这里有人会发现 这样做的话imageview就无法响应点击事件了。继续修改这个代码让iv可以响应点击事件并且可以响应

    滑动事件。

    首先修改xml 把click属性设置为true 这个代码就不上了,然后修改我们的代码 其实就是增加2个函数

    1  @Override
    2         public int getViewHorizontalDragRange(View child) {
    3             return getMeasuredWidth() - child.getMeasuredWidth();
    4         }
    5 
    6         @Override
    7         public int getViewVerticalDragRange(View child) {
    8             return getMeasuredHeight()-child.getMeasuredHeight();
    9         }

    然后看下效果:

    这个地方 如果你学过android 事件传递的话很好理解,因为如果你子view可以响应点击事件的话,那说明你消费了这个事件。

    如果你消费了这个事件话 就会先走dragger的 onInterceptTouchEvent这个方法。我们跟进去看看这个方法

     1   case MotionEvent.ACTION_MOVE: {
     2                 if (mInitialMotionX == null || mInitialMotionY == null) break;
     3 
     4                 // First to cross a touch slop over a draggable view wins. Also report edge drags.
     5                 final int pointerCount = MotionEventCompat.getPointerCount(ev);
     6                 for (int i = 0; i < pointerCount; i++) {
     7                     final int pointerId = MotionEventCompat.getPointerId(ev, i);
     8                     final float x = MotionEventCompat.getX(ev, i);
     9                     final float y = MotionEventCompat.getY(ev, i);
    10                     final float dx = x - mInitialMotionX[pointerId];
    11                     final float dy = y - mInitialMotionY[pointerId];
    12 
    13                     final View toCapture = findTopChildUnder((int) x, (int) y);
    14                     final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
    15                     if (pastSlop) {
    16                         // check the callback's
    17                         // getView[Horizontal|Vertical]DragRange methods to know
    18                         // if you can move at all along an axis, then see if it
    19                         // would clamp to the same value. If you can't move at
    20                         // all in every dimension with a nonzero range, bail.
    21                         final int oldLeft = toCapture.getLeft();
    22                         final int targetLeft = oldLeft + (int) dx;
    23                         final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
    24                                 targetLeft, (int) dx);
    25                         final int oldTop = toCapture.getTop();
    26                         final int targetTop = oldTop + (int) dy;
    27                         final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
    28                                 (int) dy);
    29                         final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
    30                                 toCapture);
    31                         final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
    32                         if ((horizontalDragRange == 0 || horizontalDragRange > 0
    33                                 && newLeft == oldLeft) && (verticalDragRange == 0
    34                                 || verticalDragRange > 0 && newTop == oldTop)) {
    35                             break;
    36                         }
    37                     }
    38                     reportNewEdgeDrags(dx, dy, pointerId);
    39                     if (mDragState == STATE_DRAGGING) {
    40                         // Callback might have started an edge drag
    41                         break;
    42                     }
    43 
    44                     if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
    45                         break;
    46                     }
    47                 }
    48                 saveLastMotion(ev);
    49                 break;
    50             }

    注意看29行到末尾 你会发现 只有当

    horizontalDragRange 和verticalDragRange  

    大于0的时候 对应的move事件才会捕获。否则就是丢弃直接丢给子view自己处理了

    另外还有一个效果就是 假如我们的 baby被拉倒了边界处,

    我们的手指不需要拖动baby这个iv,手指直接在边界的其他地方拖动此时也能把这个iv拖走。

    这个效果其实也可以实现,无非就是捕捉你手指在边界处的动作 然后传给你要拖动的view即可。

    代码非常简单 两行即可

    再重写一个回调函数 然后加个监听

    1   @Override
    2         public void onEdgeDragStarted(int edgeFlags, int pointerId) {
    3             mDragger.captureChildView(iv1, pointerId);
    4         }
    1         mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);

    这个效果在模拟器上不知道为啥 鼠标拖不动,GIF图片我就不上了大家可以自己在手机里跑一下就可以。

    上面的那些效果实际上都是DrawerLayout 等类似抽屉效果里经常用到的函数,有兴趣的同学可以

    看下源码。

  • 相关阅读:
    RabbitMQ In JAVA 介绍及使用
    利用消息队列处理分布式事务
    SpringBoot入门篇--关于properties和yml两种配置文件的一些事情
    消息队列
    多线程-- ThreadLocal
    简单ORACLE分区表、分区索引
    OR 连接查询注意
    二分查找算法(JAVA)
    逻辑回归与决策树在分类上的一些区别
    从一个R语言案例学线性回归
  • 原文地址:https://www.cnblogs.com/punkisnotdead/p/4724825.html
Copyright © 2011-2022 走看看