Android中View的事件体系(3)——自定义横向滚动viewGroup

来源:互联网 发布:杨紫为什么土 知乎 编辑:程序博客网 时间:2024/06/08 17:18

通过前几个知识,可以自定义一个横向滚动的viewGroup这个可以横向类似于viewpage+fragment而fragment中包含listView的效果,如果不做处理会有横向和纵向的滚动冲突,现在处理后可以实现既可以横向滚动又可以走纵向滚动,这里比较简单,只支持每个子view都是同样的宽高的情况,而且不支持子view的padding和margin。
具体的代码如下,里面有详细说明:

@SuppressLint("ClickableViewAccessibility")public class HorizontalScrollViewEx extends ViewGroup {    private static final String TAG = "HorizontalScrollViewEx";    /** 全部子元素的个数 */    private int mChildrenSize;    /** 每个子元素的宽度 */    private int mChildWidth;    /** 当前子元素序号 */    private int mChildIndex;    /** 最后一次触摸的X坐标 */    private int mLastX = 0;    /** 最右一次触摸的Y坐标 */    private int mLastY = 0;    /** 记录上次滑动的坐标 */    private int mLastXIntercept = 0;    /** 记录上次滑动的坐标 */    private int mLastYIntercept = 0;    /** 用来滑动到对应的位置 */    private Scroller mScroller;    /** 速度监听类 */    private VelocityTracker mVelocityTracker;    public HorizontalScrollViewEx(Context context) {        super(context);        init();    }    public HorizontalScrollViewEx(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init();    }    private void init() {        if (mScroller == null) {            mScroller = new Scroller(getContext());            mVelocityTracker = VelocityTracker.obtain();        }    }    /** 用阿里判断是否拦截当前事件 */    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        // 是否拦截的标识        boolean intercepted = false;        // 获取点击的X值        int x = (int) ev.getX();        // 获取点击的Y值        int y = (int) ev.getY();        // 分情况拦截        switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN: {            // 点击时,都不拦截点击事件            intercepted = false;            if (!mScroller.isFinished()) {                // 如果是快速滑动,则拦截点击事件,使滑动停止?                mScroller.abortAnimation();                intercepted = true;            }            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;        }        default:            break;        }        Log.d(TAG, "intercepted = " + intercepted);        // 重置最后触摸位置的坐标        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: {            // 获取水平方向的移动距离            int deltaX = x - mLastX;            // 因为是水平移动,所以这个变量并没有使用            int deltaY = y - mLastY;            // 移动到对应的地方            scrollBy(-deltaX, 0);            break;        }        case MotionEvent.ACTION_UP: {            // 获取当前滚动参数            int scrollX = getScrollX();            // 设置滚动时间            mVelocityTracker.computeCurrentVelocity(1000);            // 当水平距离大于某个值时候,移动到下一页,否则停留在当前页            float xVelocity = mVelocityTracker.getXVelocity();            if (Math.abs(xVelocity) >= 50) {                // 更新当前屏幕的页数                mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;            } else {                // 更新单前显示的页数                mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;            }            // 如果是最后一页,则显示最后一页            mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));            // 计算要滚动的距离            int dx = mChildIndex * mChildWidth - scrollX;            // 滚动到指定的位置            smoothScrollBy(dx, 0);            mVelocityTracker.clear();            break;        }        default:            break;        }        // 更新最后触摸点的坐标        mLastX = x;        mLastY = y;        return true;    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int measuredWidth = 0;        int measuredHeight = 0;        // 获得子元素个数        final int childCount = getChildCount();        // 排列子元素        measureChildren(widthMeasureSpec, heightMeasureSpec);        // 获取子元素的尺寸和模式        int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);        int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);        // 如果子元素为零        if (childCount == 0) {            // 把自己的宽高设置为0            setMeasuredDimension(0, 0);        } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {            // 如果宽和高采用了wrap_content            final View childView = getChildAt(0);            // 宽度等于所有子元素的和            measuredWidth = childView.getMeasuredWidth() * childCount;            // 高度等于子元素的高            measuredHeight = childView.getMeasuredHeight();            // 设置自己的宽高            setMeasuredDimension(measuredWidth, measuredHeight);        } else if (heightSpecMode == MeasureSpec.AT_MOST) {            // 如果高采用了wrap_content            final View childView = getChildAt(0);            // 高就是第一个子元素的高            measuredHeight = childView.getMeasuredHeight();            // 设置自己的宽高            setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());        } else if (widthSpecMode == MeasureSpec.AT_MOST) {            // 如果宽的模式是wrap_content            final View childView = getChildAt(0);            // 用第一个元素的宽度乘以子元素个数,得出自己的宽度            measuredWidth = childView.getMeasuredWidth() * childCount;            // 设置自己的宽高            setMeasuredDimension(measuredWidth, heightSpaceSize);        }        // 此处没有处理HorizontalScrollViewEx的padding,以及子元素的Margin,如果要处理时候可以遍历全部来执行    }    @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;            }        }    }    /** 滚动到对应的位置 */    private void smoothScrollBy(int dx, int i) {        // 改变坐标        mScroller.startScroll(getScrollX(), 0, dx, 0, 500);        // 重绘        invalidate();    }    /** 在父类方法中这个是一个空的实现 */    @Override    public void computeScroll() {        // 重绘过程中会调用如下方法        if (mScroller.computeScrollOffset()) {// if中的方法汇根据时间流逝来计算当前的scrollX和scrollY方法            // 这个方法返回true时候表示滑动还没有结束            // 然后移动到对饮的位置            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());            // 重绘            postInvalidate();        }    }    @Override    protected void onDetachedFromWindow() {        mVelocityTracker.recycle();        super.onDetachedFromWindow();    }}
0 0