Android使用ViewDragHelper实现简单的view拖拽和吸边功能

工作了几年,最开始做的是安卓开发,后面的做了一段时间逆向和sdk开发,一直没有系统的整理自己的知识,打算从本篇博客开始,陆续复习并记录一下自己的安卓知识

一直不知道怎么排版,先凑合着弄下

实现效果,gif上传被压扁了

 

ViewDragHelper的用法

viewDragHelper是一个安卓自带的处理拖拽的工具

先看一下viewDragHelper的创建步骤

 public static ViewDragHelper create(@NonNull ViewGroup forParent, @NonNull ViewDragHelper.Callback cb)

ViewGroup传的就是需要操作的View容器,一般我们把代码写在自定义View内,这里也就直接传this

ViewDragHelper.Callback 这个是处理拖动逻辑的核心模块,具体的方法有

public boolean tryCaptureView(@NonNull View view, int i)

这是判定规则,只有return true的时候才会去执行后续的拖动操作

这里的view是容器内被touch到的字view,只有这个view和我们需要拖动的view为同一个的时候我们才认为是匹配的

也就是 return  myView == view;

public int getViewHorizontalDragRange(@NonNull View child);

public int getViewVerticalDragRange(@NonNull View child);

容器内可以拖动的区间,只有大于0的时候才可以执行相应方向的操作,一般没有特殊要求,我们会把这个返回值设为当期容器的宽和高

 public int clampViewPositionHorizontal(@NonNull View child, int left, int dx);

 public int clampViewPositionVertical(@NonNull View child, int top, int dy);

这个返回的是被操作的view在横向或纵向所能滑出的最大距离,或者说,在x或y方向的最左或最右,最上或最下所能达到的位置,这个可以有正负,比如x方向负就直接超出左边屏幕了哈

 public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel);

这个是view被释放时候执行的操作,我们可以操作view回到指定的位置或者保持不变等

在介绍ViewDragHelper的部分方法

public boolean settleCapturedViewAt(int finalLeft, int finalTop)

把我们所操作的view平滑的滑动到指定的位置

public boolean smoothSlideViewTo(@NonNull View child, int finalLeft, int finalTop)

和上面的settleCapturedViewAt效果类似,这个可以传被滑动的指定view

但是单纯用这两个方法会发现view并没有变动,这就需要搭配下面的方法一起使用

public boolean continueSettling(boolean deferCallbacks);

public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev);

public void processTouchEvent(@NonNull MotionEvent ev);

拖动处理三件套,需要让viewDragHelper处理手势和scroller圆滑过渡

实现

首先我们定义一些扩展的变量

private View dragView;                //被拖拽的view
private ViewDragHelper viewDragHelper;
private int mWidth;                 //容器的宽度
private int mHeight;                //容器的高度
private int mChildWidth;            //拖拽的View宽度
private int mChildHeight;           //拖拽的View高度
private boolean onDrag = true;      //是否正在被拖拽
private boolean dragEnable = true;  //是否是可以拖拽的
private boolean sideEnable = true;  //是否吸边

private final int NONE = -1;

private int topFinalOffset;        //拖拽超出上边界后,释放后回到的top位置
private int bottomFinalOffset;     //拖拽超出下边界后,释放后回到的bottom位置
private int leftFinalOffset;       //拖拽超出左边界后,释放后回到的left位置
private int rightFinalOffset;      //拖拽超出右边界后,释放后回到的right位置
    
private int leftDragOffset = NONE;            //能向左拖拽的最大距离
private int rightDragOffset = NONE;            //能向右拖拽的最大距离
private int topDragOffset = NONE;             //能向上拖拽的最大距离
private int bottomDragOffset = NONE;             //能向下拖拽的最大距离

先初始化并获取一些参数

private void init() {
        viewDragHelper = ViewDragHelper.create(this, new MyDragCallBack());
}

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        //获取装载容器的宽高以及拖拽view的宽高
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        mChildHeight = dragView.getMeasuredHeight();
        mChildWidth = dragView.getMeasuredWidth();

        //默认最多可以拖拽1/2的view出屏幕
        leftDragOffset = leftDragOffset == NONE ? mChildWidth / 2 : leftDragOffset;
        rightDragOffset = rightDragOffset == NONE ? mChildWidth / 2 : rightDragOffset;
        topDragOffset = topDragOffset == NONE ? mChildHeight / 2 : topDragOffset;
        bottomDragOffset = bottomDragOffset == NONE ? mChildHeight / 2 : bottomDragOffset;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (lastChildX == 0 && lastChildY == 0) {
            calLayoutOffset();
        }
        //把view布局到相应的位置,当然第一次就是在左上角,后续位置会发生变化
        dragView.layout(lastChildX, lastChildY, lastChildX + mChildWidth, lastChildY + mChildHeight);

    }

    public void calLayoutOffset() {
        //把x,y初始化设置为最终要停留在左上角的位置
        lastChildX =leftFinalOffset;
        lastChildY =topFinalOffset;
    }

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() != 1) {
            throw new RuntimeException("child size must be 1");
        }
        dragView = getChildAt(0);
        dragView.bringToFront();
    }
 private Rect mRect = new Rect();

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (dragEnable) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    int x = (int) ev.getX();
                    int y = (int) ev.getY();
                    dragView.getHitRect(mRect);
                    onDrag = mRect.contains(x, y);
                    //如果按下的点在dragView内,则认为是拖动有效,执行viewDragHelper的方法
                    break;
            }

            if (onDrag) return viewDragHelper.shouldInterceptTouchEvent(ev);
        }
        return super.onInterceptTouchEvent(ev);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (dragEnable) {
            if (onDrag) {
                viewDragHelper.processTouchEvent(event);
                return true;
            }
        }
        return super.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        if (dragEnable) {
            if (viewDragHelper.continueSettling(true)) {
                invalidate();
            }
        }
    }

    public static int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                d

准备做完了,那么就要进行操作的代码了

 private class MyDragCallBack extends ViewDragHelper.Callback {

        @Override
        public boolean tryCaptureView(@NonNull View view, int i) {
            return dragView == view;
        }

        //以横向拖动为例
        //left是当前拖动view的左边的坐标
        //我们要做的就是让 left >= 最左的距离  同时  left <= 最右的距离
        //就是我们设置的leftDragOffset 和 rightDragOffset 
        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            leftDragOffset = leftDragOffset > mChildWidth ? mChildWidth : leftDragOffset;
            rightDragOffset = rightDragOffset > mChildWidth ? mChildWidth : rightDragOffset;

            return clamp(left, -leftDragOffset, mWidth - mChildWidth + rightDragOffset);

        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            topDragOffset = topDragOffset > mChildHeight ? mChildHeight : topDragOffset;
            bottomDragOffset = bottomDragOffset > mChildHeight ? mChildHeight : bottomDragOffset;

            return clamp(top, -topDragOffset, mHeight - mChildHeight + bottomDragOffset);

        }

        @Override
        public int getViewVerticalDragRange(@NonNull View child) {
//            return super.getViewVerticalDragRange(child);
            return mHeight;
        }

        @Override
        public int getViewHorizontalDragRange(@NonNull View child) {
//            return super.getViewHorizontalDragRange(child);
            return mWidth;
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
            if (sideEnable) {
                super.onViewReleased(releasedChild, xvel, yvel);

            //如果top小于topFinOffset则取topFinalOffset
            //如果bottom大于最大的offset则取限制的最大bottom
                int finalTop = dragView.getTop() <= topFinalOffset ? topFinalOffset : dragView.getBottom() >= mHeight - bottomFinalOffset ? mHeight - dragView.getMeasuredHeight() - bottomFinalOffset : dragView.getTop();
                lastChildY = finalTop;
                //根据left和view的一半进行界定,选择是最终停留在左边还是右边
                if (Math.abs(dragView.getLeft()) <= (getMeasuredWidth() - dragView.getMeasuredWidth()) / 2) {
                    lastChildX = leftFinalOffset;
                    //平滑过渡到相应位置
                    viewDragHelper.settleCapturedViewAt(lastChildX, finalTop);
                } else {
                    lastChildX = getMeasuredWidth() - dragView.getMeasuredWidth() - rightFinalOffset;
                    viewDragHelper.settleCapturedViewAt(lastChildX,
                            finalTop);
                }
                invalidate();
            } else {
                lastChildX = dragView.getLeft();
                lastChildY = dragView.getTop();
            }

        //把拖拽的标记定为false
            onDrag = false;


        }
    }


    //其实就是 当前和最大值取最小 同时和最小值取最大
    //如果value在两者之间直接返回value
    //如果value比最小值小,则返回min
    //如果value比最大值大,则返回max  所以是满足我们的条件的
    private int clamp(int value, int min, int max) {
        return Math.max(min, Math.min(max, value));
    }

然后建立布局文件




    
        
    


引用代码

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

        floatLayout = findViewById(R.id.layout);
        mView = findViewById(R.id.view);

        floatLayout.enableDrag(true);
        floatLayout.enableSide(true);

        //设置最大可拖拽的偏移量
        floatLayout.setFinalDragOffsets(80,80,80,80);
        //设置最终停留的位置偏移
        floatLayout.setFinalOffsets(-50);

        floatLayout.requestLayout();
        floatLayout.invalidate();


        mView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(FloatLaytoutActivity.this,"view被点击了",Toast.LENGTH_LONG).show();
            }
        });
    }

完整的代码

public class FloatLayout extends FrameLayout {

    private final int NONE = -1;
    private View dragView;
    private ViewDragHelper viewDragHelper;
    private int mWidth;
    private int mHeight;
    private int mChildWidth;
    private int mChildHeight;
    private boolean onDrag = true;
    private boolean dragEnable = true;
    private boolean sideEnable = true;  //是否吸边

    private int lastChildX;
    private int lastChildY;

    private int topFinalOffset;
    private int bottomFinalOffset;
    private int leftFinalOffset;
    private int rightFinalOffset;


    private int leftDragOffset = NONE;
    private int rightDragOffset = NONE;
    private int topDragOffset = NONE;
    private int bottomDragOffset = NONE;

    public FloatLayout(@NonNull Context context) {
        this(context, null);
    }

    public FloatLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FloatLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init() {
        viewDragHelper = ViewDragHelper.create(this, new MyDragCallBack());
    }

    public void setBottomDragOffset(int dpValue) {
        this.bottomDragOffset = dp2px(getContext(), dpValue);
    }

    public void setTopDragOffset(int dpValue) {
        this.topDragOffset = dp2px(getContext(), dpValue);
    }

    public void setLeftDragOffset(int dpValue) {
        this.leftDragOffset = dp2px(getContext(), dpValue);
    }

    public void setRightDragOffset(int dpValue) {
        this.rightDragOffset = dp2px(getContext(), dpValue);
    }

    public void setFinalOffsets(int value) {
        setFinalOffsets(value, value, value, value);
    }

    //拖拽能偏移出父容器的值,取正数
    public void setFinalDragOffsets(int left, int top, int right, int bottom) {
        setLeftDragOffset(left);
        setTopDragOffset(top);
        setRightDragOffset(right);
        setBottomDragOffset(bottom);
    }

    public void setFinalOffsets(int left, int top, int right, int bottom) {
        setLeftFinalOffset(left);
        setTopFinalOffset(top);
        setRightFinalOffset(right);
        setBottomFinalOffset(bottom);
//        calLayoutOffset();
    }

    public void setLeftFinalOffset(int dpValue) {
        this.leftFinalOffset = dp2px(getContext(), dpValue);
    }

    public void setRightFinalOffset(int dpValue) {
        this.rightFinalOffset = dp2px(getContext(), dpValue);
    }

    public void setBottomFinalOffset(int dpValue) {
        this.bottomFinalOffset = dp2px(getContext(), dpValue);
    }

    public void setTopFinalOffset(int dpValue) {
        this.topFinalOffset = dp2px(getContext(), dpValue);
    }

    public void enableDrag(boolean value) {
        dragEnable = value;
    }

    public void enableSide(boolean value) {
        sideEnable = value;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        mChildHeight = dragView.getMeasuredHeight();
        mChildWidth = dragView.getMeasuredWidth();

        leftDragOffset = leftDragOffset == NONE ? mChildWidth / 2 : leftDragOffset;
        rightDragOffset = rightDragOffset == NONE ? mChildWidth / 2 : rightDragOffset;
        topDragOffset = topDragOffset == NONE ? mChildHeight / 2 : topDragOffset;
        bottomDragOffset = bottomDragOffset == NONE ? mChildHeight / 2 : bottomDragOffset;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (lastChildX == 0 && lastChildY == 0) {
            calLayoutOffset();
        }
        dragView.layout(lastChildX, lastChildY, lastChildX + mChildWidth, lastChildY + mChildHeight);
    }

    public void calLayoutOffset() {
        lastChildX =leftFinalOffset;
        lastChildY =topFinalOffset;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() != 1) {
            throw new RuntimeException("child size must be 1");
        }
        dragView = getChildAt(0);
        dragView.bringToFront();
    }


    private class MyDragCallBack extends ViewDragHelper.Callback {

        @Override
        public boolean tryCaptureView(@NonNull View view, int i) {
            return dragView == view;
        }

        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            leftDragOffset = leftDragOffset > mChildWidth ? mChildWidth : leftDragOffset;
            rightDragOffset = rightDragOffset > mChildWidth ? mChildWidth : rightDragOffset;

            return clamp(left, -leftDragOffset, mWidth - mChildWidth + rightDragOffset);

        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            topDragOffset = topDragOffset > mChildHeight ? mChildHeight : topDragOffset;
            bottomDragOffset = bottomDragOffset > mChildHeight ? mChildHeight : bottomDragOffset;

            return clamp(top, -topDragOffset, mHeight - mChildHeight + bottomDragOffset);

        }

        @Override
        public int getViewVerticalDragRange(@NonNull View child) {
//            return super.getViewVerticalDragRange(child);
            return mHeight;
        }

        @Override
        public int getViewHorizontalDragRange(@NonNull View child) {
//            return super.getViewHorizontalDragRange(child);
            return mWidth;
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
            if (sideEnable) {
                super.onViewReleased(releasedChild, xvel, yvel);

                int finalTop = dragView.getTop() <= topFinalOffset ? topFinalOffset : dragView.getBottom() >= mHeight - bottomFinalOffset ? mHeight - dragView.getMeasuredHeight() - bottomFinalOffset : dragView.getTop();
                lastChildY = finalTop;
                if (Math.abs(dragView.getLeft()) <= (getMeasuredWidth() - dragView.getMeasuredWidth()) / 2) {
                    lastChildX = leftFinalOffset;
                    viewDragHelper.settleCapturedViewAt(lastChildX, finalTop);
                } else {
                    lastChildX = getMeasuredWidth() - dragView.getMeasuredWidth() - rightFinalOffset;
                    viewDragHelper.settleCapturedViewAt(lastChildX,
                            finalTop);
                }
                invalidate();
            } else {
                lastChildX = dragView.getLeft();
                lastChildY = dragView.getTop();
            }
            onDrag = false;


        }
    }

    private int clamp(int value, int min, int max) {
        return Math.max(min, Math.min(max, value));
    }


    private Rect mRect = new Rect();

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (dragEnable) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    int x = (int) ev.getX();
                    int y = (int) ev.getY();
                    dragView.getHitRect(mRect);
                    onDrag = mRect.contains(x, y);
                    break;
            }

            if (onDrag) return viewDragHelper.shouldInterceptTouchEvent(ev);
        }
        return super.onInterceptTouchEvent(ev);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (dragEnable) {
            if (onDrag) {
                viewDragHelper.processTouchEvent(event);
                return true;
            }
        }
        return super.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        if (dragEnable) {
            if (viewDragHelper.continueSettling(true)) {
                invalidate();
            }
        }
    }

    public static int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }

}

附上demo地址 https://github.com/gouptosee/ViewDragHelperDemo

 

你可能感兴趣的:(Android,Android)