在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