经过在自定义的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