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

通过前几个知识,可以自定义一个横向滚动的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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值