使用ViewDragHelper自定义左右可滑动内容的ViewGroup
通过在自定义的ViewGroup内部使用ViewDragHelper,使得给自定义的ViewGroup在水平方向上并排按序添加多个子View(ViewGroup),可以实现水平左右滚动的效果,类似于ViewPager.官方解释如下(不做翻译,原汁原味的英语更易理解):/** * 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.val helper : ViewDragHelper = ViewDragHelper.create(this, object : ViewDragHelper.Callback(){ //根据需要,重写相关的方法.})在你的自定义ViewGroup的onTouchEvent(event)方法内调用ViewDragHelper.processTouchEvent(event). override fun onTouchEvent(event: MotionEvent): Boolean { helper.processTouchEvent(event) return true }在ViewDragHelper.processTouchEvent(event)方法内部调用了Callback的回调方法.这样你只需要重写Callback的回调方法即可.Callback先看一下我们需要用到的Callback的方法.tryCaptureView当前触摸到的是哪个View,我们定义的这个ViewGroup可以添加多个子Viewoverride 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 }clampViewPositionHorizontal(@NonNull View child, int left, int dx)约束水平方向上左右可滚动的边界位置.对于通过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 }clampViewPositionVertical(@NonNull View child, int top, int dy)竖直方向上的顶部和底部的边界约束.我们这里不做处理,直接返回0.onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,int dy)当前触摸的view位置发生改变时的回调.需要对每个子view都重新更改其位置.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 } } }onViewReleased(@NonNull View releasedChild, float xvel, float yvel)松开手指后的回调.releaseChild.getLeft() > 0;向右滚动.需要判断releaseChild滚动的距离有没有超过其前一个View的宽度的一半.releaseChild.getLeft() < 0;向左滚动.需要判断releaseChild滚动的距离有没有超过自身的宽度的一半.int getViewHorizontalDragRange(@NonNull View child);水平滚动的范围.这里等于各个子view宽度之和.int getViewVerticalDragRange(@NonNull View child);竖直方向上不做滚动,直接返回0即可.具体源码看这里ScrollerLayout ...