Android 自定义 View 学习 (十五)——ViewDragHelper 入门学习
学习资料:
Android开发群英传
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.
ViewDragHelper
是一个自定义ViewGroup
的工具类。内部提供了一系列属性和用户拖动状态,并且支持恢复
ViewDragHelper
是解决各种滑动的终极绝招,几乎可以实现各种不同的的滑动、拖动
1. 简单使用
简单需求:
在屏幕上,一个TextView
可以拖动,并且当拖动的距离位于屏幕上半部分1/2
区域内,可以自己恢复原始位置
ViewDragHelper
使用也有一个固定的模式
- 初始化
ViewDragHer
实例,并创建所需要的回调接口 - 处理事件拦截和事件的消费
代码:
public class DragView extends LinearLayout { private ViewDragHelper mViewDragHelper; public DragView(Context context, AttributeSet attrs) { super(context, attrs); initDragHelper(); } private void initDragHelper() { mViewDragHelper = ViewDragHelper.create(DragView.this, 1.0f, mDragCallback); } / * ViewDragHelper回调接口 */ private ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) {//可以用来指定哪一个childView可以拖动 return true; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) {// 水平拖动 return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) {//竖直拖动 return top; } }; @Override public boolean onInterceptHoverEvent(MotionEvent event) {//拦截事件 return mViewDragHelper.shouldInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) {//消费事件 //将触摸事件传递给`ViewDragHelper`,必不可少 mViewDragHelper.processTouchEvent(event); return true; }}
布局文件:
布局文件很简单,就是包含一个TextView
简单使用
在DragView
内,TextView
就可以任意拖动
在创建ViewDragHelper
对象时,create()
方法有两种形式
//方式 1public static ViewDragHelper create(ViewGroup forParent, Callback cb) { return new ViewDragHelper(forParent.getContext(), forParent, cb);}//方式2public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) { final ViewDragHelper helper = create(forParent, cb); helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); return helper;}
方式2
多了第二个参数值,代表了灵敏度。sensitivity
越大,helper.mTouchSlop
越小,一般写为1.0f
在ViewDragHelper.Callback mDragCallback
内,重写了3个方法
@Overridepublic boolean tryCaptureView(View child, int pointerId) { return true;}
这个方法可以用来指定哪一个childView
可以进行拖动,通过重写onFinishInflate()
来获取childView
子控件,然后进行childView
判断
@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// 水平拖动 return left;}@Overridepublic int clampViewPositionVertical(View child, int top, int dy) {//竖直拖动 return top;}
这两个方法默认返回值都为0
left
代表在水平方向上,childView
即将在x
轴移动到目标坐标位置,dx
代表较前一次的增量,left = child.getLeft()+dx
,top
同理,代表垂直方向上childView
即将在y
轴移动到目标坐标位置
虽然上面的代码实现了childView
的拖动,但有些问题需要考虑优化
2.简单进行Padding优化
在简单使用
使用图中,首先一个问题便是TextView
超出了屏幕范围,导致内容都无法显示完全。需要对left
和top
进行修改
修改代码
@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// 水平拖动 final int leftPadding = getPaddingLeft(); final int rightPadding = getWidth() - child.getWidth() - getPaddingRight(); return Math.min(Math.max(left, leftPadding), rightPadding);}@Overridepublic int clampViewPositionVertical(View child, int top, int dy) {//竖直拖动 final int topPadding = getPaddingTop(); final int bottomPadding = getHeight() - child.getHeight() - getPaddingBottom(); return Math.min(Math.max(top, topPadding), bottomPadding);}
移动范围
可以移动的区域就是灰色区域,水平范围便是paddingLeft <= target <= getWidth()-child.getWidth()-getPaddingRight()
,垂直方向同理。padding
四边的值不一定相同。
3.恢复到默认位置
当在竖直方向上,拖动不超过DragView
高度的一半,就会回弹到默认位置
完整代码
public class DragView extends LinearLayout { private ViewDragHelper mViewDragHelper; private Point initPoint; private View autoTextView; public DragView(Context context, AttributeSet attrs) { super(context, attrs); initDragHelper(); } private void initDragHelper() { mViewDragHelper = ViewDragHelper.create(DragView.this, 1.0f, mDragCallback); initPoint = new Point(); } / * ViewDragHelper回调接口 */ private ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { return true; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) {// 水平拖动 final int leftPadding = getPaddingLeft(); final int rightPadding = getWidth() - child.getWidth() - leftPadding; final int newLeft = Math.min(Math.max(left, leftPadding), rightPadding); return newLeft; } @Override public int clampViewPositionVertical(View child, int top, int dy) {//竖直拖动 final int topPadding = getPaddingTop(); final int bottomPadding = getHeight() - child.getHeight() - topPadding; final int newTop = Math.min(Math.max(top, topPadding), bottomPadding); return newTop; } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) {//拖动结束后 super.onViewReleased(releasedChild, xvel, yvel); if (releasedChild == autoTextView && releasedChild.getTop() < (getHeight()/2)){ mViewDragHelper.smoothSlideViewTo(releasedChild,initPoint.x,initPoint.y);//平滑移动 ViewCompat.postInvalidateOnAnimation(DragView.this); } } }; @Override public boolean onInterceptHoverEvent(MotionEvent event) {//拦截事件 return mViewDragHelper.shouldInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) {//消费事件 mViewDragHelper.processTouchEvent(event); return true; } @Override public void computeScroll() { super.computeScroll(); if (mViewDragHelper.continueSettling(true)) {//不停计算位置后,自动移动 ViewCompat.postInvalidateOnAnimation(DragView.this);//重新绘制 } } / * 完成解析布局xml文件 */ @Override protected void onFinishInflate() { super.onFinishInflate(); autoTextView = getChildAt(0); } / * 布局 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); initPoint.x = autoTextView.getLeft(); initPoint.y = autoTextView.getTop(); }}
利用onLayout()
方法,拿到TextView
起始点一开始的初始化坐标( initPoint.x,initPoint.y)
,在onViewReleased()
方法中,进行结束拖动后的处理
4.处理Button
在DragView
中,添加一个Button
或者给TextView
添加android:clickable="true",android:longClickable="true"
,便不能进行拖动处理
原因根据前面学过的,Button
的clickable
默认为ture
,事件被消费了,DragView
便不会再处理ACTION_MOVE,ACTION_UP
事件,Button
的clickable
设置为false
后,便可以拖动,但此时Button
却不在可以点击
想要实现Button
既可以点击又又可以拖动,需要在ViewDragHelper.Callback mDragCallback
重写两个方法
@Overridepublic int getViewHorizontalDragRange(View child) { return getMeasuredWidth() - child.getMeasuredWidth();}@Overridepublic int getViewVerticalDragRange(View child) { return getMeasuredHeight() - child.getMeasuredHeight();}
注意:
因为Button
是可以点击的,当ACTION_DOWN
事件发生时(也就是手指落在按钮上),之后的ACTION_MOVE,ACTION_UP
便会由Button
处理,需要从Button
外的区域滑到Button
内后,Button
再才会跟随手指动作被拖动
5. 边缘拖动
在使用一些侧滑的控件时,有些可以从手机屏幕最左侧边缘滑出,ViewDragHelper.Callback mDragCallback
提供了回调方法,使用有两个步骤
- 第1步:指定边缘拖动目标控件
边缘拖动代码:
@Overridepublic void onEdgeDragStarted(int edgeFlags, int pointerId) { super.onEdgeDragStarted(edgeFlags, pointerId); mViewDragHelper.captureChildView(autoTextView,pointerId);}
使用captureChildView()
方法来指定childView
主动进行边缘拖动回调方法操作
- 第2步:在初始化
ViewDragHelper
时,指定边缘方向
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
方向共有5个值:
ViewDragHelper.EDGE_ALL 四周都可以ViewDragHelper.EDGE_LEFT 左边缘ViewDragHelper.EDGE_RIGHT 右边缘ViewDragHelper.EDGE_TOP 顶部ViewDragHelper.EDGE_BOTTOM 底部
6.Callback中的其他回调方法
方法 | 作用 |
---|---|
onViewDragStateChanged(int state) | ViewDragHelper拖动状态发生改变,STATE_IDLE ,STATE_DRAGGING ,STATE_SETTLING[自动滚动] ,分别对应0,1,2 |
onViewPositionChanged() | 拖动目标childView 位置发生改变 |
onViewCaptured(View capturedChild, int activePointerId) | 调用captureChildView 确定拖动目标时,回调此方法 |
onEdgeTouched(int edgeFlags, int pointerId) | 触摸ViewGroup 边缘 |
onEdgeLock(int edgeFlags) | true的时候会锁住当前的边界,false则unLock |
getOrderedChildIndex(int index) | 默认返回传入的index,可以重写将控件重新排序 |
加上上面用过的方法,Callback
的回调方法就这些
7. ViewDragHelper常用方法
方法 | 作用 |
---|---|
cancel() | 取消拖动 |
abort() | 取消拖动的过程,直接将控件移动了指定位置 |
captureChildView(View childView, int activePointerId) | 将指定的子控件移动到指定位置 |
continueSettling(boolean deferCallbacks) | 自动不断计算位置后移动控件 |
smoothSlideViewTo(View child, int finalLeft, int finalTop) | 将child 平滑移动到指定的位置 |
settleCapturedViewAt(int finalLeft, int finalTop) | 以手指离开时的速度为初速度,将控件移动到指定的位置 |
shouldInterceptTouchEvent(MotionEvent ev) | 判断父容器是否应该拦截事件 |
processTouchEvent(MotionEvent ev) | 处理触摸事件由父视图接收 |
其他的以后用到了再学习补充
8.最后
都是方法的简单调用
本人很菜,有错误请指出
共勉 : )
文/英勇青铜5
关键字:android, 产品经理
版权声明
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!