Android自定义ViewGroup神器-ViewDragHelper
投稿作者:zhuhf 原文链接:http://www.jianshu.com/p/111a7bc76a0e 特别声明:本文为zhuhf原创并授权发布,未经原作者允许请勿转载,转载请联系原作者。
一、概述
ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state tracking for allowing a user to drag and reposition views within their parent ViewGroup.
这是官方的解释:在自定义ViewGroup时,ViewDragHelper可以用来拖拽和设置子View的位置(在ViewGroup范围内)。另外,还提供了一系列的方法和状态跟踪。
可见,在自定义ViewGroup时,ViewDragHelper一般用来处理子View的位置移动。
二、入门示例
效果很简单,屏幕中间有两个TextView,位置随着我们的手指不断移动。
传统方式实现:一般需要重写onInterceptTouchEvent和onTouchEvent这两个方法,写好这两个方法不是一件容易的事情,需要自己去处理:事件冲突、加速检测等。
ViewDragHelper简化了很多工作,让我们更加关注“业务”的需求,实现步骤如下:
- 创建ViewDragHelper实例
- 处理ViewGroup的触摸事件
- ViewDragHelper.Callback的编写
(一) 自定义ViewGroup
VDHLinearLayout的代码还是非常简单的,主要是分为以下三个步骤:
- 创建ViewDragHelper实例 dragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {}); 创建需要三个参数,第一个为当前的ViewGroup,第二个为sensitivity,主要用于设置touchSlop: helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); 传入越大,touchSlop就越小。第三个参数就是ViewDragHelper.Callback,触摸过程中会回调相关方法。
- 实现ViewDragHelper.Callback相关方法
- 处理ViewGroup触摸事件
onInterceptTouchEvent直接交给dragHelper.shouldInterceptTouchEvent去处理,onTouchEvent通过dragHelper.processTouchEvent来处理。 如果你希望拖拽的子View是不可点击的,可以不重写onInterceptTouchEvent方法,后面我们会介绍为什么。
(二) 布局文件
布局很简单,自定义的ViewGroup包含两个TextView。
三、更多用法
ViewDragHelper不仅仅能够让子View跟随我们的手指移动,还能实现以下功能:
- 边界触摸检测
- Drag释放回调
- 移动到某个指定位置
我么改造下上面的例子,效果图如下:
第一个View,可以随意被拖动位置 第二个View,只能从ViewGroup左侧拖动 第三个View,拖动释放之后会回到原始位置
修改后的ViewGroup代码如下:
- tryCaptureView方法,我们只捕获第一个和第三个View,分别是dragView和autoBackView。
- 使用dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)设置ViewGroup左边缘可以被拖拽,同时在ViewDragHelper.Callback的onEdgeDragStarted方法中,使用dragHelper.captureChildView主动去捕获第二个View:edgeDragView。 虽然在tryCaptureView方法中我们并未捕获edgeDragView,但dragHelper.captureChildView可以绕过该方法,详见官方解释: Capture a specific child view for dragging within the parent. The callback will be notified but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to capture this view.
- onViewReleased方法会在被捕获的子View释放之后调用,我们判断释放的View:releasedChild是autoBackView,使用dragHelper.settleCapturedViewAt方法设置autoBackView的位置为它的初始位置。 注意,此方法内部是通过Scroller实现的,所以我们需要使用invalidate来刷新,同时需要重写computeScroll方法:
@Overridepublic void computeScroll() {
if (dragHelper.continueSettling(true))
{
invalidate();
}
}
dragHelper.continueSettling方法是用来判断当前被捕获的子View是否还需要继续移动,类似Scroller的computeScrollOffset方法一样,我们需要在返回true的时候使用invalidate刷新。
至此,我么已经介绍了ViewDragHelper以及ViewDragHelper.Callback的多数用法。
还记得前面我们留下的一个问题吗?
“如果你希望拖拽的子View是不可点击的,可以不重写onInterceptTouchEvent方法,后面我们会介绍为什么。”
我们尝试将TextView设置成clickable=true,你会发现原本可以被拖拽的View都不动了。我们思考下,这是为什么呢?
原因在于:
由于子View是可被点击的,那么会触发ViewGroup的onInterceptTouchEvent方法。默认情况下,事件会被子View消耗掉,这显然是有问题的,因为这样ViewGroup的onTouch方法就不会被调用,而onTouch方法中正是我们的关键方法:dragHelper.processTouchEvent。
既然我们找到原因了,有人说:你不能在onInterceptTouchEvent直接返回true吗?为啥还要用dragHelper.shouldInterceptTouchEvent(ev)的返回值啊???
确实,如果你直接返回true,会发现一切都能正常工作了。
这里我们需要解释下:
打个比方,如果你的ViewGroup中有另外一个Button(或者任何可点击的View),但是它不在ViewDragHelper的处理范围内,你可能需要监听它的onClick事件,如果直接返回true,你会发现onClick事件不会被触发了。
纳尼,为啥呢?因为ViewGroup拦截了它的事件了啊。。。好吧,我们还是老实这样写吧:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
}
你迫不及待的运行修改之后的代码。咦?为啥还是不能拖拽。。。 此时,遇到这种情况,我一般是查看下dragHelper.shouldInterceptTouchEvent的源码(此处省略了部分不相关的代码):
shouldInterceptTouchEvent返回true的条件是mDragState == STATE_DRAGGING,然而mDragState是在tryCaptureViewForDrag方法中被设置为STATE_DRAGGING的。
所以,如果horizontalDragRange == 0 && verticalDragRange == 0这个条件一直为true的话,tryCaptureViewForDrag方法就得不到调用了。
而horizontalDragRange和verticalDragRange分别是Callback的getViewHorizontalDragRange和getViewVerticalDragRange方法返回的值,这两个方法默认情况下都返回0。
- getViewHorizontalDragRange,返回子View水平方向可以被拖拽的范围
- getViewVerticalDragRange,返回子View垂直方向可以被拖拽的范围
我们尝试重写这两个方法:
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
@Override
public int getViewHorizontalDragRange(View child) {
return getMeasuredWidth() - child.getMeasuredWidth();
}
再次运行下,你会发现TextView设置clickable=true之后也可以被拖拽了。
至此,ViewDragHelper的基本使用方式我们已经介绍完了。详细的代码可以查看文章最后的源码,另外,源码中还实现了一个比较常用的效果:
本文源码:https://github.com/hiphonezhu/Android-Demos/tree/master/ViewDragHelperDemo
- JavaScript 基础(一)
- 我也来说说.net开源
- 是时候对员工进行网络安全培训了:黑客正将目标瞄准打印机
- 进度条ProgressBar
- Microsoft Visual Studio International Pack
- 柯洁5冠在手“食言”再战AI:我已看开 输赢无所谓
- JGulp: 利用Gulp 配置的前端项目自动化工作流
- 微软Enterprise Library 4.0将支持依赖注入
- 时钟AnalogClock与DigitalClock
- 细数那些在2017年被黑客滥用的系统管理工具和协议
- Compass: 在你的应用中集成搜索功能
- 列表选择Spinner
- 巧用CSS3 :target 伪类制作Dropdown下拉菜单(无JS)
- 开源的虚拟机软件 VirtualBox v1.5.2
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 「通信框架Netty4 源码解读(一)」起步,关于IO的简单总结,模拟一个redis客户端
- Unet实现文档图像去噪、去水印
- 「influxDB 原理与实践(一)」安装部署,实现基础的添加删除查询功能
- 「influxDB 原理与实践(二)」详解influxDB的写入与查询
- Nginx系列:https配置
- 笛卡尔积、等值连接、自然连接、外连接一文看懂
- nginx系列:常用利用shell统计日志
- Nginx系列:图片过滤处理
- Nginx系列:几款负载均衡第三方插件的安装与使用
- 「高并发通信框架Netty4 源码解读(三)」NIO缓冲区Buffer详解
- UML类图符号:各种关系说明以及举例
- 「高并发通信框架Netty4 源码解读(四)」NIO缓冲区之字节缓冲区ByteBuffer详解
- 「influxDB 原理与实践(三)」连续查询
- 为什么使用OPA而不是原生的Pod安全策略?
- 浅入浅出 Java ConcurrentHashMap