Android ViewDragHelper 使用详解

一、简介

  对于编写一个自定义的viewgroup ViewDragHelper是一个实用的类。它提供了很多有用的操作和状态,实现监听用户在父ViewGroup拖拽和重新定位子view。

二、详解

1. 使用步骤

(1)、自定义一个View

(2)、创建一个ViewDragHelper的实例

    dragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
    });

  其中1.0f是敏感度参数参数越大越敏感。this为当前的ViewGroup,他是ViewDragHelper的拖动处理对象,必须为ViewGroup。

(3)、触摸相关的方法的调用

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return dragHelper.shouldInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        dragHelper.processTouchEvent(event);
        return true;
    }

  将Tonch事件交给ViewDragHelper处理

(4)、实现ViewDragHelper.CallCack相关方法

2. 类介绍

(1)、ViewDragHelper常用方法介绍

dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_TOP | ViewDragHelper.EDGE_RIGHT | ViewDragHelper.EDGE_BOTTOM);

  设置边界触摸回调是否被激活,分别为上下左右边界的设置

dragHelper.smoothSlideViewTo(view, getPaddingLeft(), getPaddingTop())

  使用动画平滑的移动到指定位置,但是一定要添加下边代码

 @Override
 public void computeScroll() {
     if (dragHelper.continueSettling(true)) {
         ViewCompat.postInvalidateOnAnimation(this);
     }
 }

  还有一些其他功能方法,如(取消、状态跟踪)的方法,比较好理解这里不做介绍。

(2)、ViewDragHelper.Callback的类介绍,在下边示例注释中已经非常完善,这里不做重复介绍了。

3. 相关问题

(1)、如果ViewGroup的子控件会消耗点击事件,例如按钮,在触摸屏幕的时候就会先走onInterceptTouchEvent方法,判断是否可以捕获,而在判断的过程中会去判断另外两个回调的方法:getViewHorizontalDragRange和getViewVerticalDragRange,只有这两个方法返回大于0的值才能正常的捕获,否则会出现子控件不能拖动的问题。

(2)、如果需要使用边界检测需要添加上

dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_TOP | ViewDragHelper.EDGE_RIGHT | ViewDragHelper.EDGE_BOTTOM);

(3)、如果你当前布局的根布局不是DragLayout,可能会遇到DragLayout遮挡其他布局Touch事件的问题。这时你可以考虑onTouchEvent(),onInterceptTouchEvent()方法,根据需求动态返回true,false。

三、示例

1. 示例效果

示例效果

2. 代码分析

DragLayout.java


import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;


public class DragLayout extends LinearLayout {
    private static final String TAG = "DragLayout";
    private ViewDragHelper dragHelper;

    private TextView mDragView1;
    private View mDragView2;
    private View mDragView3;

    public DragLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        dragHelper = ViewDragHelper.create(this, callback);

        // 设置边界触摸回调被激活
        dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_TOP | ViewDragHelper.EDGE_RIGHT | ViewDragHelper.EDGE_BOTTOM);
    }

    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         * 指定View是否可拖拽
         * @param child 用户捕获的view
         * @param pointerId
         * @return 捕获的view是否可拖动
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == mDragView1 || child == mDragView3;
        }

        /**
         * @param state 拖拽的状态
         *
         * @see # STATE_IDLE 拖拽结束
         * @see # STATE_DRAGGING 正在拖拽
         * @see # STATE_SETTLING 正在被放置,这个状态不会出现
         */
        @Override
        public void onViewDragStateChanged(int state) {
            if (state == ViewDragHelper.STATE_DRAGGING) {
                mDragView1.setText("正在拖拽");
            } else if (state == ViewDragHelper.STATE_IDLE) {
                mDragView1.setText("拖拽结束");
            }
        }

        /**
         * 当View的位置发生改变是回调
         * @param changedView 当前发生位置改变的View
         * @param left view左上角新X坐标
         * @param top view左上角新Y坐标
         * @param dx 俩次回调X轴方向移动的距离
         * @param dy 俩次回调Y轴方向移动的距离
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            Log.d(TAG, "onViewPositionChanged left" + left);
            Log.d(TAG, "onViewPositionChanged top" + top);
            Log.d(TAG, "onViewPositionChanged dx" + dx);
            Log.d(TAG, "onViewPositionChanged dy" + dy);
            Log.d(TAG, "----------------------");
        }

        /**
         * 当view被捕获时回调
         *
         * @param capturedChild 被捕获的view
         * @param activePointerId
         */
        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {

        }

        /**
         * 手指释放时候回调该方法
         * @param releasedChild 被释放的子view
         * @param xvel 手指的X轴速度,以像素每秒钟离开屏幕
         * @param yvel 手指的Y轴速度,以像素每秒钟离开屏幕
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            // 向下,向右为正,否则为负
            if (yvel > 0) {
                if (releasedChild == mDragView3) {
                    smoothToBottom(releasedChild);
                }
                Toast.makeText(getContext(), "释放时,速度向下大于0", Toast.LENGTH_SHORT).show();
            } else if (yvel < 0) {
                if (releasedChild == mDragView3) {
                    smoothToTop(releasedChild);
                }
                Toast.makeText(getContext(), "释放时,速度向上大于0", Toast.LENGTH_SHORT).show();
            }
        }

        /**
         * 当触摸到父布局边界时回调(必须调用ViewDragHelper.setEdgeTrackingEnabled才会生效)
         *
         * @param edgeFlags
         * @param pointerId
         */
        @Override
        public void onEdgeTouched(int edgeFlags, int pointerId) {
            if (edgeFlags == ViewDragHelper.EDGE_TOP) {
                Toast.makeText(getContext(), "到上边界了", Toast.LENGTH_SHORT).show();
            } else if (edgeFlags == ViewDragHelper.EDGE_BOTTOM) {
                Toast.makeText(getContext(), "到下边界了", Toast.LENGTH_SHORT).show();
            } else if (edgeFlags == ViewDragHelper.EDGE_LEFT) {
                Toast.makeText(getContext(), "到左边界了", Toast.LENGTH_SHORT).show();
            } else if (edgeFlags == ViewDragHelper.EDGE_RIGHT) {
                Toast.makeText(getContext(), "到右边界了", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public boolean onEdgeLock(int edgeFlags) {
            return false;
        }

        /**
         * 当没有捕获到子view时,通过触摸边界拖拽子view(场景:当view在屏幕外,无法捕获view,只能通过触摸屏幕边界先让他滚回来)
         * @param edgeFlags
         * @param pointerId
         */
        @Override
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
            dragHelper.captureChildView(mDragView2, pointerId);
//            dragHelper.captureChildView(mDragView3, pointerId);
        }

        /**
         * 返回捕获的view Z轴的坐标(即当前view在布局的第几层)
         * @param index
         * @return
         */
        @Override
        public int getOrderedChildIndex(int index) {
            Log.d(TAG, "getOrderedChildIndex index" + index);
            return index;
        }

        /**
         * 该view水平方向拖动的范围(子控件消耗点击事件时候才回调(例如:按钮))
         * @param child
         * @return
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return getMeasuredWidth() - child.getMeasuredWidth();
        }

        /**
         * 该view垂直方向拖动的范围(子控件消耗点击事件时候才回调(例如:按钮))
         * @param child
         * @return
         */
        @Override
        public int getViewVerticalDragRange(View child) {
            return getMeasuredHeight() - child.getMeasuredHeight();
        }

        /**
         * 垂直方向限制View的拖动范围
         * @param child 被拖动的子View
         * @param top 移动过程中Y轴的坐标
         * @param dy
         * @return view的新位置
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            // 限定view上下边界(以view左上角坐标点为准)
            final int topBound = getPaddingTop();
            final int bottomBound = getHeight() - child.getHeight();
            final int newTop = Math.min(Math.max(top, topBound), bottomBound);
            return newTop;
        }

        /**
         * 水平方向限制View的拖动范围
         * @param child 被拖动的子View
         * @param left 移动过程中X轴的坐标
         * @param dx
         * @return view的新位置
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        }
    };

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return dragHelper.shouldInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        dragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mDragView1 = (TextView) getChildAt(0);
        mDragView2 = getChildAt(1);
        mDragView3 = getChildAt(2);
    }

    public void smoothToTop(View view) {
        // 动画平滑的移动到指定位置
        if (dragHelper.smoothSlideViewTo(view, getPaddingLeft(), getPaddingTop())) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    public void smoothToBottom(View view) {
        // 动画平滑的移动到指定位置
        if (dragHelper.smoothSlideViewTo(view, getPaddingLeft(), getHeight() - view.getHeight())) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    public void computeScroll() {
        if (dragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}

XML文件

<com.wyj.viewdraglayout.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textview1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center"
        android:layout_margin="10dp"
        android:background="#FFDCD665"
        android:gravity="center"
        android:text="可直接拖动的View" />

    <TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center"
        android:layout_margin="10dp"
        android:background="#FFDCD665"
        android:gravity="center"
        android:text="触摸边界可拖动的View" />

    <Button
        android:id="@+id/button"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        android:background="#FFDCD665"
        android:gravity="center"
        android:text="Button" />


</com.wyj.viewdraglayout.DragLayout>

3. 示例代码下载

github地址 https://github.com/Ya-Jun/SmallSampleCollection

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
CountDownLatch是Java中的一个同步工具类,用于控制多个线程的执行顺序。它通过一个计数器来实现,该计数器初始化为一个正整数,每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器的值减到0时,所有等待的线程就会被唤醒继续执行。 在Android中,CountDownLatch可以用于等待多个异步操作完成后再执行某个任务,或者等待多个线程都达到某个状态后再继续执行。 下面是CountDownLatch的基本使用方法: 1. 创建CountDownLatch对象,并指定计数器的初始值。 ```java CountDownLatch latch = new CountDownLatch(3); // 计数器初始值为3 ``` 2. 在需要等待的地方调用`await()`方法,使当前线程进入等待状态,直到计数器值为0时才会继续执行。 ```java try { latch.await(); // 等待计数器值为0 } catch (InterruptedException e) { e.printStackTrace(); } ``` 3. 在需要通知其他线程继续执行的地方调用`countDown()`方法,将计数器的值减1。 ```java latch.countDown(); // 计数器减1 ``` 下面是一个简单的示例,演示了如何使用CountDownLatch等待多个线程完成后再执行某个任务: ```java import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { final int threadNum = i; new Thread(() -> { try { Thread.sleep(1000); System.out.println("Thread " + threadNum + " completed."); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } try { latch.await(); System.out.println("All threads completed. Start executing the task..."); // 执行任务 } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 在上面的示例中,我们创建了一个初始值为3的CountDownLatch对象。然后创建了3个线程,每个线程会在执行完任务后将计数器减1。主线程调用`await()`方法等待计数器的值变为0,当所有线程都执行完任务后,主线程才会继续执行并输出"All threads completed. Start executing the task..."。 希望对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ya-Jun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值