在Android中避免不了自定义ViewGroup,来实现咱们原生控件所不能知足的需求。尤为是复杂的ViewGroup实现,手势的处理是避免不了的。咱们要针对不一样的ViewGroup来实现不一样的onInterceptTouchEvent
与onTouchEvent
事件等。android
那么有没有什么简便的方法呢?答案是确定的,ViewDragHelper能够帮助咱们解决负责的手势操做。它是官方所提供的一个专门为自定义ViewGroup处理拖拽的手势类。下面是官方的原文引用说明git
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.
经过这篇文章你将会掌握如下几个知识点:github
首先须要构建ViewDragHelper的实例,经过它的静态create
方法生成segmentfault
mViewDragHelper = ViewDragHelper.create(this, object : ViewDragHelper.Callback() { override fun tryCaptureView(child: View?, pointerId: Int): Boolean { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } })
主要参数为ViewGroup
与ViewDragHelper.Callback
。Callback是对view操做的回调,绝对多数手势操做都是在这个回调中完成。tryCaptureView
方法是它惟一的抽象方法,默认须要实现。根据参数child判断用户触摸的view是否能够进行后续操做。api
为了让ViewDragHelper帮助咱们简化手势操做,因此还需为它传入相关的MotionEvent
。ide
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { return mViewDragHelper.shouldInterceptTouchEvent(ev) } override fun onTouchEvent(event: MotionEvent?): Boolean { mViewDragHelper.processTouchEvent(event) return true }
分别调用ViewDragHelper的shouldInterceptTouchEvent
与processTouchEvent
来简化手势的操做判断。将手势操做所有交由ViewDragHelper来实现。动画
若是要处理惯性滑动,再重写computeScroll
方法this
override fun computeScroll() { if (mViewDragHelper.continueSettling(true)) { invalidate() } }
ViewDragHelper的基本使用就是这么多,算了一下也就十几行代码。相对于本身实现其中的细节,减小了许多代码。因此若是你想快速简便的实现手势操做,ViewDragHelper是不二之选。spa
下面经过一个实例来对ViewDragHelper的主要Api的使用进行分析。首先来看下要实现的初步效果。code
有三个view,分别能够进行水平、竖直与任意位置滑动。而要实现这种效果,须要用到的就是ViewDragHelper.Callback中的回调方法。
该方法返回布尔值来判断当前操做的view是否能够进行捕获。demo中须要这三个view都能被捕获到,因此很简单只需与参数的child作对比便可。
override fun tryCaptureView(child: View?, pointerId: Int): Boolean { if (mLeft == 0 || mTop == 0){ mLeft = mFlexibleView.left mTop = mFlexibleView.top } return child == mHorizontalView || child == mVerticalView || child == mFlexibleView }
初始化了任意滑动view的初始left与top,以便后续使用。
有了view的捕获判断,接下来对水平方向的操做进行判断。
override fun clampViewPositionHorizontal(child: View?, left: Int, dx: Int): Int { if (child != mVerticalView) { return left } return child.left }
它的各个参数与返回值
因为只有竖直方向的view不能随意移动,因此当捕获的view为竖直方向时就直接返回child.left原来的位置;反之返回left。
对于竖直方向的操做判断与水平方向同理,看下代码便可。
override fun clampViewPositionVertical(child: View?, top: Int, dy: Int): Int { if (child != mHorizontalView) { return top } return child.top }
此时运行项目,该demo的功能基本完成,三个view都能预期拖动。只是要到达任意view拖动以后回到初始位置还需重写接下来的方法。
override fun onViewReleased(releasedChild: View?, xvel: Float, yvel: Float) { if (releasedChild == mFlexibleView) { mViewDragHelper.settleCapturedViewAt(mLeft, mTop) invalidate() } else { super.onViewReleased(releasedChild, xvel, yvel) } }
这是对view释放后的回调。若是要对view释放后的轨迹作改变能够在这方法中实现。
这里还使用到了settleCapturedViewAt
方法,该方法的做用是将当前view定位到所给的坐标位置。内部会回到continueSettling
方法,来实现滑动动画。
xvel与yvel能够用来实现释放view后的惯性移动操做。
从头至尾只使用了ViewDragHelper.Callback中的四个回调方法,就实现了demo中的拖拽效果。相对于本身实现,简单程度不言而喻。因此熟练使用ViewDragHelper不只能提升咱们的实现效率与代码质量还能减小出错率。
对于其它的Api都是些状态改变的回调,在实际中也用的少,手势的操做逻辑都不会在这些Api中实现,因此这里就很少介绍。上面的demo view拖拽超出边界,若是要固定边界,只需在clampViewPositionHorizontal与clampViewPositionVertical中边界判断便可,具体实现留给读者思考。
下面有一个注意的点:若是使用Button或者TextView设置了clickable=true,你会发现上面的Demo中的view不能操做了。主要缘由是,若是view不消费触摸事件,则触摸事件将直接进入onTouchEvent
,在Down事件中就已经肯定了捕获的view;若是消费事件,会进入onInterceptTouchEvent
判断是否能够捕获,而判断的过程会去调用getViewHorizontalDragRange
与getViewVerticalDragRange
,只有这两个回调方法返回大于0的值才能被捕获。因此要解决拖拽不动的问题,只需重写这两个方法。
override fun getViewHorizontalDragRange(child: View?): Int { return measuredWidth - (child?.measuredWidth?:0) } override fun getViewVerticalDragRange(child: View?): Int { return measuredHeight - (child?.measuredHeight?:0) }
ok,到这里ViewDragHelper已经彻底入门了,而且关键的Api也已经了然于胸。下面是实际项目中使用ViewDragHelper的效果图,与饿了么的商品详情界面效果相似。
上面的手势动画使用的就是ViewDragHelper,而用到的Api也是所有是文章中提到的。但愿经过文章中的demo效果可以加深ViewDragHelper对你们的影响与对它的指望度,也但愿在平常开发中可以帮助你们轻松解决手势问题。
文章中的代码均可以在Github获取到。使用时请将分支切换到feat_viewdraghelper_dev
Android Architecture Components Part1:Room