ViewDragHelper解析

ViewDragHelper

它主要用于处理ViewGroup中对子View的拖拽处理,本质是对触摸事件的解析类。

ViewDrager是简化view拖拽操作的帮助类,ViewDragHelper解决了android中手势处理过于复杂的问题。

1.ViewDragHelper在高版本的v4包(android4.4以上的v4)中

2.它主要用于处理ViewGroup中对子View的拖拽处理

3.它是Google在2013年开发者大会提出的

4.它主要封装了对View的触摸位置,触摸速度,移动距离等的检测和Scroller,通过接口回调的
方式告诉我们;只需要我们指定是否需要移动,移动多少等;

5.本质是对触摸事件的解析类;

View移动的相关方法总结

1.通过改变view在父View的layout位置来移动,但是只能移动指定的View:

view.layout(l,t,r,b);
view.offsetLeftAndRight(offset);//同时改变left和right
view.offsetTopAndBottom(offset);//同时改变top和bottom

2.通过改变scrollX和scrollY来移动,但是可以移动所有的子View;

scrollTo(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(x,y)表示移动的增量为dx,dy。

scrollTo(x,y);
scrollBy(xOffset,yOffset);

3.通过改变Canvas绘制的位置来移动View的内容:

canvas.drawBitmap(bitmap, left, top, paint)

4.LayoutParams

//必须获取父View的LayoutParams 
        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)getLayoutParams();
        layoutParams.leftMargin = getLeft() + dx;
        layoutParams.topMargin = getTop() + dy;
        setLayoutParams(layoutParams);

案例

接下来就实现一个内部View随意拖动的demo

这里写图片描述

如上图简单呈现出两个方块后,提出一个需求:

1.拖动方块时,方块(即子View)可以跟随手指移动。
2.一个方块移动时,另一个方块可以跟随移动。
3.将方块移动到左边区域(右边区域)后放开(即手指离开屏幕),它会自动移动到左边界(右边界)。
4.移动的时候给方块加点动画 。

自定义一个ViewGroup继承FrameLayout:

在自定义ViewGroup的时候,如果对子View的测量没有特殊的需求,那么可以继承系统已有的
布局(比如FrameLayout),目的是为了让已有的布局帮我们实行onMeasure;

package com.xiaoyehai.viewdragerhelper.widget;

import android.content.Context;
import android.graphics.Color;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

import com.nineoldandroids.view.ViewHelper;
import com.xiaoyehai.viewdragerhelper.ColorUtil;

/**
 * 继承FrameLayout
 * 在自定义ViewGroup的时候,如果对子View的测量没有特殊的需求,那么可以继承系统已有的
 * 布局(比如FrameLayout),目的是为了让已有的布局帮我们实行onMeasure;
 * <p>
 * Created by xiaoyehai on 2018/8/3 0003.
 */

public class DragLayout extends FrameLayout {

    /**
     * ViewDragHelper:它主要用于处理ViewGroup中对子View的拖拽处理,
     * 本质是对触摸事件的解析类;
     */
    private ViewDragHelper mViewDragHelper;

    private View redView;

    private View blueView;

    public DragLayout(Context context) {
        this(context, null);
    }

    public DragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        init();
    }

    /**
     * 初始化ViewDragHelper
     */
    private void init() {
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    /**
     * 当DragLayout的布局文件的结束标签读取完成后会执行该方法,此时会知道自己有几个子控件
     * 一般用来初始化子控件的引用
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        redView = getChildAt(0);
        blueView = getChildAt(1);
    }

    /**
     * 确定控件位置
     *
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //放在左上角
        int left = getPaddingLeft();
        int top = getPaddingTop();

        //放在水平居中
        //int left = getPaddingLeft() + getMeasuredWidth() / 2 - redView.getMeasuredWidth() / 2;

        //摆放在左上角
        redView.layout(left, top, left + redView.getMeasuredWidth(),
                top + redView.getMeasuredHeight());

        //摆放在redView下面
        blueView.layout(left, redView.getBottom(), left + blueView.getMeasuredWidth(),
                redView.getBottom() + blueView.getMeasuredHeight());
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 让ViewDragHelper帮我们判断是否应该拦截
        boolean result = mViewDragHelper.shouldInterceptTouchEvent(ev);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 将触摸事件交给ViewDragHelper来解析处理
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    /**
     * 回调类
     */
    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         * 用于判断是否捕获当前child的触摸事件
         * @param child 当前触摸的子View
         * @param pointerId
         * @return true:捕获并解析  false:不处理
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == blueView || child == redView;
        }

        /**
         * 当view被开始捕获和解析的回调
         * @param capturedChild 当前被捕获的子view
         * @param activePointerId
         */
        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            super.onViewCaptured(capturedChild, activePointerId);
        }

        /**
         * 获取view水平方向的拖拽范围,但是目前不能限制边界,返回的值目前用在手指抬起的时候
         *  view缓慢移动的动画时间的计算; 最好不要返回0
         * @param child
         * @return
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return getMeasuredWidth() - child.getMeasuredWidth();
        }

        /**
         * 获取view垂直方向的拖拽范围,目前不能限制边界,最好不要返回0
         * @param child
         * @return
         */
        @Override
        public int getViewVerticalDragRange(View child) {
            return getMeasuredHeight() - child.getMeasuredHeight();
        }

        /**
         * 控制child在水平方向的移动
         * @param child 当前触摸的子View
         * @param left 当前child的即将移动到的位置,left=chile.getLeft()+dx
         * @param dx 本次child水平方向移动的距离
         * @return 表示你真正想让child的left变成的值
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (left < 0) {
                left = 0; //限制左边界
            } else if (left > getMeasuredWidth() - child.getMeasuredWidth()) {
                left = getMeasuredWidth() - child.getMeasuredWidth(); //限制右边界
            }
            // return left-dx; //不能移动
            return left;
        }

        /**
         * 控制child在垂直方向的移动
         * @param child
         * @param top  top=child.getTop()+dy
         * @param dy
         * @return
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            if (top < 0) {
                top = 0; //限制上边界
            } else if (top > getMeasuredHeight() - child.getMeasuredHeight()) {
                top = getMeasuredHeight() - child.getMeasuredHeight(); //限制下边界
            }
            return top;
        }

        /**
         * 当child的位置改变的时候执行,一般用来做其他子View跟随该view移动
         * @param changedView 当前位置改变的child
         * @param left child当前最新的left
         * @param top child当前最新的top
         * @param dx 本次水平移动的距离
         * @param dy 本次垂直移动的距离
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);

            //让redView跟着blueView移动
            if (changedView == blueView) {
                redView.layout(redView.getLeft() + dx, redView.getTop() + dy,
                        redView.getRight() + dx, redView.getBottom() + dy);
            } else if (changedView == redView) {
                //让blueView跟着redView移动
                blueView.layout(blueView.getLeft() + dx, blueView.getTop() + dy,
                        blueView.getRight() + dx, blueView.getBottom() + dy);
            }
            //1.计算view移动百分比:当前位置/总长
            float fraction = changedView.getLeft() * 1f / (getMeasuredWidth() - changedView.getMeasuredWidth());
            //2.执行伴随动画
            excuteAnim(fraction);
        }

        /**
         * 手指抬起的执行该方法
         * @param releasedChild 当前抬起的view
         * @param xvel x方向的移动速度有 正:向右移动
         * @param yvel 方向的移动速度
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);

            int centerLeft = getMeasuredWidth() / 2 - releasedChild.getMeasuredWidth() / 2;
            if (releasedChild.getLeft() < centerLeft) {
                //控件在左半边,向左缓慢移动
                //ViewDragHelper封装了Scroller
                mViewDragHelper.smoothSlideViewTo(releasedChild, 0, releasedChild.getTop());
                ViewCompat.postInvalidateOnAnimation(DragLayout.this); //刷新
            } else {
                //控件在右半边,向右缓慢移动
                mViewDragHelper.smoothSlideViewTo(releasedChild,
                        getMeasuredWidth() - releasedChild.getMeasuredWidth(), releasedChild.getTop());
                ViewCompat.postInvalidateOnAnimation(DragLayout.this); //刷新
            }
        }
    };

    @Override
    public void computeScroll() {
        //如果动画还没结束
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(DragLayout.this); //刷新
        }
    }

    /**
     * 执行移动过程中的伴随动画
     *
     * @param fraction 移动百分比
     */
    private void excuteAnim(float fraction) {
        //缩放
        //        redView.setScaleX(1 + 0.5f * fraction);
        //        redView.setScaleY(1 + 0.5f * fraction);

        //ViewHelper.setScaleX(redView, 1 + 0.5f * fraction);
        //ViewHelper.setScaleX(blueView, 1 + 0.5f * fraction);
        //ViewHelper.setScaleY(redView, 1 + 0.5f * fraction);


        //旋转
        // ViewHelper.setRotation(redView, 720 * fraction); //围绕z抽转
        ViewHelper.setRotationX(redView, 360 * fraction); //围绕x抽转
        ViewHelper.setRotationX(blueView, 360 * fraction); //围绕x抽转
        //ViewHelper.setRotationX(redView, 360 * fraction);
        // ViewHelper.setRotationY(redView,720 * fraction); //围绕y抽转

        //平移
        // ViewHelper.setTranslationX(redView,80 * fraction);

        //透明度
        // ViewHelper.setAlpha(redView,1 - fraction);

        //设置 redView过度颜色的渐变
        //redView.setBackgroundColor((Integer) ColorUtil.evaluateColor(fraction, Color.RED, Color.GREEN));
        //设置该控件过度颜色的渐变
        //setBackgroundColor((Integer) ColorUtil.evaluateColor(fraction, Color.RED, Color.GREEN));
    }

}

使用:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xiaoyehai.viewdragerhelper.MainActivity">

    <com.xiaoyehai.viewdragerhelper.widget.DragLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#f00" />

        <TextView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#00f" />

    </com.xiaoyehai.viewdragerhelper.widget.DragLayout>

</android.support.constraint.ConstraintLayout>
package com.xiaoyehai.viewdragerhelper;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

/**
 * ViewDragHelper:它主要用于处理ViewGroup中对子View的拖拽处理,
 * 本质是对触摸事件的解析类.
 */
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

ColorUtil:

package com.xiaoyehai.viewdragerhelper;

public class ColorUtil {
    public static Object evaluateColor(float fraction, Object startValue,
            Object endValue) {
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
                | (int) ((startR + (int) (fraction * (endR - startR))) << 16)
                | (int) ((startG + (int) (fraction * (endG - startG))) << 8)
                | (int) ((startB + (int) (fraction * (endB - startB))));
    }
}

继承 ViewGroup也可实现

getHeight和getMeasuredHeight的区别:

getMeasuredHeight:只要view执行完onMeasure方法就能够获取到值;
getHeight:只有view执行完layout才能获取到值;

package com.xiaoyehai.viewdragerhelper.widget;

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.nineoldandroids.view.ViewHelper;


/**
 * getHeight和getMeasuredHeight的区别:
 * getMeasuredHeight:只要view执行完onMeasure方法就能够获取到值;
 * getHeight:只有view执行完layout才能获取到值;
 * <p>
 * Created by xiaoyehai on 2018/8/3 0003.
 */

public class DragLayout2 extends ViewGroup {

    private View redView;

    private View blueView;

    /**
     * ViewDragHelper:它主要用于处理ViewGroup中对子View的拖拽处理,
     * 本质是对触摸事件的解析类;
     */
    private ViewDragHelper mViewDragHelper;

    public DragLayout2(Context context) {
        this(context, null);
    }

    public DragLayout2(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragLayout2(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        init();
    }

    /**
     * 初始化ViewDragHelper
     */
    private void init() {
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    /**
     * 当DragLayout的布局文件的结束标签读取完成后会执行该方法,此时会知道自己有几个子控件
     * 一般用来初始化子控件的引用
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        redView = getChildAt(0);
        blueView = getChildAt(1);
    }

    /**
     * 测量控件大小::宽度和高度
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //要测量自己的子View
        // View view = getChildAt(0); //获取子view,不这样用,在onFinishInflate()方法中获取

        //int size = (int) getResources().getDimension(R.dimen.width); //100dp
        int size = redView.getLayoutParams().width;
        //测量规则,宽和高相同,用同一个测量规则
        int measureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        //父view测量完子view可填(0,0),现在是正要测量子View,不能写(0,0)
        redView.measure(measureSpec, measureSpec); //宽,高
        blueView.measure(measureSpec, measureSpec);

        //如果说没有特殊的对子View的测量需求,可以用如下方法测量比较简单
        //measureChild(redView, widthMeasureSpec, heightMeasureSpec);
        //        measureChild(blueView, widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * 确定控件位置
     *
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //放在左上角
        int left = getPaddingLeft();
        int top = getPaddingTop();

        //放在水平居中
        //int left = getPaddingLeft() + getMeasuredWidth() / 2 - redView.getMeasuredWidth() / 2;

        //摆放在左上角
        redView.layout(left, top, left + redView.getMeasuredWidth(),
                top + redView.getMeasuredHeight());

        //摆放在redView下面
        blueView.layout(left, redView.getBottom(), left + blueView.getMeasuredWidth(),
                redView.getBottom() + blueView.getMeasuredHeight());
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 让ViewDragHelper帮我们判断是否应该拦截
        boolean result = mViewDragHelper.shouldInterceptTouchEvent(ev);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 将触摸事件交给ViewDragHelper来解析处理
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    /**
     * 回调类
     */
    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         * 用于判断是否捕获当前child的触摸事件
         * @param child 当前触摸的子View
         * @param pointerId
         * @return true:捕获并解析  false:不处理
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == blueView || child == redView;
        }

        /**
         * 当view被开始捕获和解析的回调
         * @param capturedChild 当前被捕获的子view
         * @param activePointerId
         */
        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            super.onViewCaptured(capturedChild, activePointerId);
        }

        /**
         * 获取view水平方向的拖拽范围,但是目前不能限制边界,返回的值目前用在手指抬起的时候
         *  view缓慢移动的动画时间的计算; 最好不要返回0
         * @param child
         * @return
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return getMeasuredWidth() - child.getMeasuredWidth();
        }

        /**
         * 获取view垂直方向的拖拽范围,目前不能限制边界,最好不要返回0
         * @param child
         * @return
         */
        @Override
        public int getViewVerticalDragRange(View child) {
            return getMeasuredHeight() - child.getMeasuredHeight();
        }

        /**
         * 控制child在水平方向的移动
         * @param child 当前触摸的子View
         * @param left 当前child的即将移动到的位置,left=chile.getLeft()+dx
         * @param dx 本次child水平方向移动的距离
         * @return 表示你真正想让child的left变成的值
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (left < 0) {
                left = 0; //限制左边界
            } else if (left > getMeasuredWidth() - child.getMeasuredWidth()) {
                left = getMeasuredWidth() - child.getMeasuredWidth(); //限制右边界
            }
            // return left-dx; //不能移动
            return left;
        }

        /**
         * 控制child在垂直方向的移动
         * @param child
         * @param top  top=child.getTop()+dy
         * @param dy
         * @return
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            if (top < 0) {
                top = 0; //限制上边界
            } else if (top > getMeasuredHeight() - child.getMeasuredHeight()) {
                top = getMeasuredHeight() - child.getMeasuredHeight(); //限制下边界
            }
            return top;
        }

        /**
         * 当child的位置改变的时候执行,一般用来做其他子View跟随该view移动
         * @param changedView 当前位置改变的child
         * @param left child当前最新的left
         * @param top child当前最新的top
         * @param dx 本次水平移动的距离
         * @param dy 本次垂直移动的距离
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);

            //让redView跟着blueView移动
            if (changedView == blueView) {
                redView.layout(redView.getLeft() + dx, redView.getTop() + dy,
                        redView.getRight() + dx, redView.getBottom() + dy);
            } else if (changedView == redView) {
                //让blueView跟着redView移动
                blueView.layout(blueView.getLeft() + dx, blueView.getTop() + dy,
                        blueView.getRight() + dx, blueView.getBottom() + dy);
            }
            //1.计算view移动百分比:当前位置/总长
            float fraction = changedView.getLeft() * 1f / (getMeasuredWidth() - changedView.getMeasuredWidth());
            //2.执行伴随动画
            excuteAnim(fraction);
        }

        /**
         * 手指抬起的执行该方法
         * @param releasedChild 当前抬起的view
         * @param xvel x方向的移动速度有 正:向右移动
         * @param yvel 方向的移动速度
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);

            int centerLeft = getMeasuredWidth() / 2 - releasedChild.getMeasuredWidth() / 2;
            if (releasedChild.getLeft() < centerLeft) {
                //控件在左半边,向左缓慢移动
                //ViewDragHelper封装了Scroller
                mViewDragHelper.smoothSlideViewTo(releasedChild, 0, releasedChild.getTop());
                ViewCompat.postInvalidateOnAnimation(DragLayout2.this); //刷新
            } else {
                //控件在右半边,向右缓慢移动
                mViewDragHelper.smoothSlideViewTo(releasedChild,
                        getMeasuredWidth() - releasedChild.getMeasuredWidth(), releasedChild.getTop());
                ViewCompat.postInvalidateOnAnimation(DragLayout2.this); //刷新
            }
        }
    };

    @Override
    public void computeScroll() {
        //如果动画还没结束
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(DragLayout2.this); //刷新
        }
    }

    /**
     * 执行移动过程中的伴随动画
     *
     * @param fraction 移动百分比
     */
    private void excuteAnim(float fraction) {
        //缩放
        //        redView.setScaleX(1 + 0.5f * fraction);
        //        redView.setScaleY(1 + 0.5f * fraction);

        //ViewHelper.setScaleX(redView, 1 + 0.5f * fraction);
        //ViewHelper.setScaleX(blueView, 1 + 0.5f * fraction);
        //ViewHelper.setScaleY(redView, 1 + 0.5f * fraction);


        //旋转
        // ViewHelper.setRotation(redView, 720 * fraction); //围绕z抽转
        ViewHelper.setRotationX(redView, 360 * fraction); //围绕x抽转
        ViewHelper.setRotationX(blueView, 360 * fraction); //围绕x抽转
        //ViewHelper.setRotationX(redView, 360 * fraction);
        // ViewHelper.setRotationY(redView,720 * fraction); //围绕y抽转

        //平移
        // ViewHelper.setTranslationX(redView,80 * fraction);

        //透明度
        // ViewHelper.setAlpha(redView,1 - fraction);

        //设置 redView过度颜色的渐变
        //redView.setBackgroundColor((Integer) ColorUtil.evaluateColor(fraction, Color.RED, Color.GREEN));
        //设置该控件过度颜色的渐变
        //setBackgroundColor((Integer) ColorUtil.evaluateColor(fraction, Color.RED, Color.GREEN));
    }
}

代码链接

github地址

猜你喜欢

转载自blog.csdn.net/qq_36699930/article/details/81383701
今日推荐