代码源自《Android开发艺术探索》第三章 View的事件体系 3.5节 View的滑动冲突
(1)父容器,onInterceptTouchEvent 不拦截 ACTION_DOWN (2)父容器,ACTION_MOVE 中,根据滑动方向,判断由谁处理滑动 (3)如果由父容器处理,则拦截 ACTION_MOVE;如果由 子View处理,则不拦截 ACTION_MOVE (4)父容器,在 onTouchEvent 中,处理 ACTION_MOVE,通过 scrollBy 实现随手指滑动 (5)父容器,在 onTouchEvent 中,处理 ACTION_UP,通过 Scroller 实现平滑移动 可以通过 VelocityTracker 获取滑动速度,根据速度确定滑动方向。速度达到某临界值,做特殊处理等。 (6)父容器,实现 onMeasure() onLayout(),实现测量、布局定位
package com.xxx.xxx.xxx.test; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.widget.Scroller; /** * 外部拦截法,解决与子元素的滑动冲突 */ public class HorizontalScrollViewEx extends ViewGroup { private int mChildrenSize; private int mChildWidth; private int mChildIndex; //分别记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; //分别记录上次滑动的坐标(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalScrollViewEx(Context context) { this(context, null); } public HorizontalScrollViewEx(Context context, AttributeSet attrs) { this(context, attrs, 0); } public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: intercepted = false; if (!mScroller.isFinished()) {//父容器的滚动还没结束 mScroller.abortAnimation();//中断滚动动画 intercepted = true;//拦截ACTION_DOWN } break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; //根据距离差判断滑动方向 if (Math.abs(deltaX) > Math.abs(deltaY)) { //横向滑动,外部父容器拦截 intercepted = true; } else { intercepted = false; } break; case MotionEvent.ACTION_UP: intercepted = false; break; } mLastX = x; mLastY = y; mLastXIntercept = x; mLastYIntercept = y; return intercepted; } @Override public boolean onTouchEvent(MotionEvent event) { mVelocityTracker.addMovement(event); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) {//父容器的滚动还没结束 mScroller.abortAnimation();//中断滚动动画 } break; case MotionEvent.ACTION_MOVE: //处理父容器拦截下来事件,ACTION_MOVE 时的水平滑动 int deltaX = x - mLastX; int deltaY = y - mLastY; //ACTION_MOVE 滑动时父容器的滚动实现 scrollBy(-deltaX, 0); break; case MotionEvent.ACTION_UP: //处理父容器拦截下来事件,松手时的水平滑动 //松手时,父容器的平滑移动实现 int scrollX = getScrollX(); int scrollToChildIndex = scrollX / mChildWidth; mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) >= 50) {//水平速度绝对值 超过50像素每秒 /* 水平速度大于0,说明是手指是从右向左滑动,切到下一页,mChildIndex递增; * 速度小于0,手指是从左向右滑动,切到上一页,mChildIndex递减 */ mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; } else {//水平速度绝对值 小于 50像素每秒 /* scrollX:父容器内容左边 与 父容器左边缘距离;也就是当前父容器 X轴方向偏移距离 * 这个算法表示:scrollX 如果达到了子元素(子元素宽度默认是屏幕宽度,模拟ViewPager的子元素)的 n + 0.5 倍,n为正整数 * 比如,scrollX < 子元素宽度的0.5倍,那么 mChildIndex 为0 * scrollX >= 子元素宽度0.5倍 && scrollX < 子元素宽度,那么 mChildIndex 为1 * scrollX == 子元素宽度的 1.5倍数,那么 mChildIndex为 2 */ mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; } mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); int dx = mChildIndex * mChildWidth - scrollX; smoothScrollBy(dx, 0);//平滑滑动到目标index 位置 mVelocityTracker.clear(); break; } mLastX = x; mLastY = y; return true; } private void smoothScrollBy(int dx, int dy) { mScroller.startScroll(getScrollX(), 0, dx, 0, 500); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) {//是否还在滑动 scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//滑动到当前mScroller位置 postInvalidate(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measureWidth = 0; int measureHeight = 0; final int childCount = getChildCount(); //测量子元素尺寸 measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measureWidth = childView.getMeasuredWidth() * childCount; measureHeight = childView.getMeasuredHeight(); setMeasuredDimension(measureWidth, measureHeight); } else if (heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measureHeight = childView.getMeasuredHeight(); setMeasuredDimension(widthSpecSize, measureHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measureWidth = childView.getMeasuredWidth() * childCount; setMeasuredDimension(measureWidth, heightSpecSize); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } }