經過在自定義的ViewGroup內部使用ViewDragHelper,使得給自定義的ViewGroup在水平方向上並排按序添加多個子View(ViewGroup),能夠實現水平左右滾動的效果,相似於ViewPager.java
官方解釋以下(不作翻譯,原汁原味的英語更易理解):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. */
ViewDragHelper內部定義了一個靜態內部類Callback,咱們須要重寫Callback.github
val helper : ViewDragHelper = ViewDragHelper.create(this, object : ViewDragHelper.Callback(){ //根據須要,重寫相關的方法. })
在你的自定義ViewGroup的onTouchEvent(event)方法內調用ViewDragHelper.processTouchEvent(event).app
override fun onTouchEvent(event: MotionEvent): Boolean { helper.processTouchEvent(event) return true }
在ViewDragHelper.processTouchEvent(event)方法內部調用了Callback的回調方法.這樣你只須要重寫Callback的回調方法便可.ide
先看一下咱們須要用到的Callback的方法.this
當前觸摸到的是哪一個View,咱們定義的這個ViewGroup能夠添加多個子Viewidea
override fun tryCaptureView(capturedView: View, pointerId: Int): Boolean { for (x in 0 until childCount) { val child = getChildAt(x) if (child.visibility == View.GONE) continue if (child == capturedView) return true; } return false }
約束水平方向上左右可滾動的邊界位置.對於經過tryCaptureView觸摸的任意一個view,須要對它的左右兩個方向作邊界約束.翻譯
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int { for (x in 0 until childCount) { if (getChildAt(x) == child) { //左邊界約束,在ScrollerLayout未發生滑動的狀況下,當前觸摸的子View距離ScrollerLayout的左邊界的距離值. var clampLeft = 0 //右邊界約束,在ScrollerLayout未發生滑動的狀況下,當前觸摸的子View距離ScrollerLayout的右邊界的距離值. var clampRight = 0 for (y in 0 until x) { clampLeft += getChildAt(y).width } for (y in x + 1 until childCount) { clampRight += getChildAt(y).width } //當前觸摸的子View距離ScrollerLayout的左邊界不能超過clampLeft的約束值,子View向右滑動的極限 if (left > clampLeft) return clampLeft //當前觸摸的子View距離ScrollerLayout的右邊界不能超過clampRight的約束值,子View向左滑動的極限 if (left + clampRight < 0) return clampRight } } return left }
豎直方向上的頂部和底部的邊界約束.咱們這裏不作處理,直接返回0.code
當前觸摸的view位置發生改變時的回調.須要對每一個子view都從新更改其位置.get
override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) { super.onViewPositionChanged(changedView, left, top, dx, dy) for (x in 0 until childCount) { if (getChildAt(x) == changedView) { changedView.layout(left, 0, left + changedView.width, height) //當前觸摸的子View左右兩邊的View的left值,也就是距離ScrollerLayout的左邊界的距離. var totalChildWidth: Int = 0 //對於changedView左側的View,採用由右至左的順序來改變每一個view的位置.方便totalChildWidth作累加操做 for (y in x - 1 downTo 0) { val child = getChildAt(y) totalChildWidth += child.width child.layout(left - totalChildWidth, top, left - (totalChildWidth - child.width), height) } //changedView右側的第一個View距離ScrollerLayout的左邊界的默認距離 totalChildWidth = changedView.width+left //對於changedView右側的,採用由左至右的順序來改變每一個view的位置. for (y in x + 1 until childCount) { val child = getChildAt(y) child.layout(totalChildWidth, 0, child.width + totalChildWidth, height) totalChildWidth += child.width } break } } }
鬆開手指後的回調.
水平滾動的範圍.這裏等於各個子view寬度之和.
豎直方向上不作滾動,直接返回0便可.
具體源碼看這裏ScrollerLayout