Android 自定义Viewgroup之仿360手机助手详情页图片滑动

1、概述

这是作者的第一篇文章,如果哪里写的不好请提出来,作者将及时改进。

本文将一步一步带你了解自定义ViewGroup的onLayout方法和layout
还有滑动冲突事件的解决。可以说自定义ViewGroup除了没有深入去研究onMeasure别的都有了。

阅读本文前请先理解View的X轴Y轴原理,还有layout和scrollTo的用法。
效果图
自己下载源码玩玩看

2、准备工作

我们需要先获取屏幕的宽度,然后获取屏幕一半的宽度,用于决定最高点,因为最高点的X轴是在屏幕的正中间。三分之一屏幕的宽度用于子View的宽度。

public class HorizontalScrollView extends ViewGroup {

    private int windowWidth;
    private int halfWindowWidth;//一半的屏幕宽度
    private int oneThirdWindowWidth;//三分之一屏幕宽度

    public HorizontalScrollView(Context context) {
        this(context, null);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        windowWidth = wm.getDefaultDisplay().getWidth();//获取屏幕宽度
        halfWindowWidth = windowWidth / 2;
        oneThirdWindowWidth = windowWidth / 3;
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);//测量子类
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }

}

3、重写onLayout排列子View

public class HorizontalScrollView extends ViewGroup {

    private int windowWidth;
    private int halfWindowWidth;//一半的屏幕宽度
    private int oneThirdWindowWidth;//三分之一屏幕宽度

    private int childLeftLeft;//左边第一个的left值
    private int childRightLeft;//右边第一个的left值
    private int childCount;//子View个数
    private int childHalfCount;
    private int childViewHeightDifference = 50;//我们把子View的高度差设为50


    public HorizontalScrollView(Context context) {
        this(context, null);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        windowWidth = wm.getDefaultDisplay().getWidth();//获取屏幕宽度
        halfWindowWidth = windowWidth / 2;
        oneThirdWindowWidth = windowWidth / 3;
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        childLeftLeft = halfWindowWidth;
        childRightLeft = halfWindowWidth;
        childCount = getChildCount();
        childHalfCount = childCount / 2;
        for (int i = childHalfCount; i >= 0; i--) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = oneThirdWindowWidth;
                childView.layout(childLeftLeft - childWidth / 2, childViewHeightDifference * (childHalfCount - i), childLeftLeft - childWidth / 2 + childWidth,
                        childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());

                childLeftLeft -= childWidth;
            }
        }

        for (int i = childHalfCount + 1; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = oneThirdWindowWidth;
                childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
                        childRightLeft + childWidth / 2 + childWidth, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
                childRightLeft += childWidth;
            }
        }

    }

}

这里我们进行子View的排列,把子View的排列分为两部分,一部分为左边,一部分为右边。
如果子View的个数是偶数,那么左边的for循环需要管理的是总个数一半在加一个,而右边是一半还要减一个。这里就是为什么右边循环体要加1。
childLeftLeft -= childWidth;用于排列下一个子View的时候位置向左移动一个子View的宽度。同时他们的高度也要减少一个高度差。
childRightLeft += childWidth;用于排列下一个子View的时候位置向右移动一个子View的宽度。同时他们的高度也要减少一个高度差。

4、ViewGroup滑动处理

public class HorizontalScrollView extends ViewGroup {

    private int windowWidth;
    private int halfWindowWidth;//一半的屏幕宽度
    private int oneThirdWindowWidth;//三分之一屏幕宽度

    private int childLeftLeft;//左边第一个的left值
    private int childRightLeft;//右边第一个的left值
    private int childCount;//子View个数
    private int childHalfCount;
    private int childViewHeightDifference = 50;//我们把子View的高度差设为50


    private int startX;
    private int lastX;
    private int totalX;
    public HorizontalScrollView(Context context) {
        this(context, null);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        windowWidth = wm.getDefaultDisplay().getWidth();//获取屏幕宽度
        halfWindowWidth = windowWidth / 2;
        oneThirdWindowWidth = windowWidth / 3;
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        childLeftLeft = halfWindowWidth;
        childRightLeft = halfWindowWidth;
        childCount = getChildCount();
        childHalfCount = childCount / 2;
        for (int i = childHalfCount; i >= 0; i--) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = oneThirdWindowWidth;
                childView.layout(childLeftLeft - childWidth / 2, childViewHeightDifference * (childHalfCount - i), childLeftLeft - childWidth / 2 + childWidth,
                        childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());

                childLeftLeft -= childWidth;
            }
        }

        for (int i = childHalfCount + 1; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = oneThirdWindowWidth;
                childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
                        childRightLeft + childWidth / 2 + childWidth, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
                childRightLeft += childWidth;
            }
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        startX = (int) event.getRawX();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                break;
            case MotionEvent.ACTION_MOVE:

                int  delatX = startX - lastX;

                totalX += delatX;
                scroll();


                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        lastX = startX;
        return true;
    }


    private void scroll() {
        scrollTo(-totalX, 0);//左右滑动实际上是滑动ViewGroup的内容。

    }
}

这里我们添加了一个onTouchEvent然后去算出总共滚动了多少,在去调用自己写的scroll()方法去滚动我们的View。
这个时候我们就可以看看刚刚layout的样子了。

5、分别让两边子View动起来

public class HorizontalScrollView extends ViewGroup {

    /**
     * 每一步我们重点关心每一步新添加的变量。
     */
    //2、准备工作
    private int windowWidth;
    private int halfWindowWidth;//一半的屏幕宽度
    private int oneThirdWindowWidth;//三分之一屏幕宽度
    //3、重写onLayout排列子View
    private int childLeftLeft;//左边第一个的left值
    private int childRightLeft;//右边第一个的left值
    private int childCount;//子View个数
    private int childHalfCount;
    private int childViewHeightDifference = 50;//我们把子View的高度差设为50

    //4、ViewGroup滑动处理
    private int startX;
    private int lastX;
    private int totalX;

    //5、分别让两边子View动起来
    private int touchChildCenterCount;//互动时位于屏幕正中间的View
    private int leftHigh;
    private int rightHigh;

    public HorizontalScrollView(Context context) {
        this(context, null);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        windowWidth = wm.getDefaultDisplay().getWidth();//获取屏幕宽度
        halfWindowWidth = windowWidth / 2;
        oneThirdWindowWidth = windowWidth / 3;
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        childLeftLeft = halfWindowWidth;
        childRightLeft = halfWindowWidth;
        childCount = getChildCount();
        childHalfCount = childCount / 2;
        leftHigh = childViewHeightDifference * (childHalfCount);//初始化左边for循环的最左边View的高度位置
        rightHigh = childViewHeightDifference * (-childHalfCount);//初始化右边for循环的最左边View的高度位置,需要加上i * childViewHeightDifference
        touchChildCenterCount = childHalfCount;
        for (int i = childHalfCount; i >= 0; i--) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = oneThirdWindowWidth;
                childView.layout(childLeftLeft - childWidth / 2, childViewHeightDifference * (childHalfCount - i), childLeftLeft - childWidth / 2 + childWidth,
                        childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());

                childLeftLeft -= childWidth;
            }
        }

        for (int i = childHalfCount + 1; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = oneThirdWindowWidth;
                childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
                        childRightLeft + childWidth / 2 + childWidth, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
                childRightLeft += childWidth;
            }
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        startX = (int) event.getRawX();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                break;
            case MotionEvent.ACTION_MOVE:

                int delatX = startX - lastX;

                totalX += delatX;
                scroll();


                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        lastX = startX;
        return true;
    }


    private void scroll() {

        childLeftLeft = halfWindowWidth;
        childRightLeft = halfWindowWidth;


        for (int i = touchChildCenterCount; i >= 0; i--) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = childView.getMeasuredWidth();
                childView.layout(childLeftLeft - childWidth / 2, leftHigh - i * childViewHeightDifference,
                        childLeftLeft + childWidth / 2, leftHigh - i * childViewHeightDifference + childView.getMeasuredHeight());
                childLeftLeft -= childWidth;
            }

        }
        for (int i = touchChildCenterCount + 1; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = childView.getMeasuredWidth();
                childView.layout(childRightLeft + childWidth / 2, rightHigh + i * childViewHeightDifference,
                        childRightLeft + childWidth / 2 + childWidth, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
                childRightLeft += childWidth;
            }
        }


        leftHigh = -totalX * childViewHeightDifference * oneThirdWindowWidth + childViewHeightDifference * (childHalfCount);
        rightHigh = totalX * childViewHeightDifference * oneThirdWindowWidth + childViewHeightDifference * (-childHalfCount);

        scrollTo(-totalX, 0);//左右滑动实际上是滑动ViewGroup的内容。

    }
}

首先我们定义左边for循环的最左边View的高度位置和右边for循环的最左边View的高度位置,然后在onLayout方法中给他赋值。

然后是scroll()方法中
childLeftLeft = halfWindowWidth;
childRightLeft = halfWindowWidth;
每次调用的时候需要重新赋值防止for循环后改变他们的值,下一步我们也会用到他们。
leftHigh = -totalX * childViewHeightDifference/oneThirdWindowWidth + childViewHeightDifference * (childHalfCount);
rightHigh = totalX* childViewHeightDifference/oneThirdWindowWidth + childViewHeightDifference * (-childHalfCount);
我们先来说说这两行代码,这里加号后面的是初始化的高度,至于+前面的根据x轴大小的改变,来改变y轴的值;也就是说高度的改变是通过左右滑动来获得的。当滑动屏幕的三分之一时高度改变一个高度差,也就是高度改变我们设置的childViewHeightDifference。这是一种函数的思想,可能比较难理解不过大家想想以前的一次函数就明白了,通过X轴的改变来得到Y轴的改变。
然后for循环中我们就不在定死每一个子View的高度值,而是根据高度变化来计算,别的就不变。
接下来我们要处理一下为什么上升到最高点不下降还继续上升了,这个是最难的。

6、处理上升到最高点之后下降

首先我要说怎么处理上升到最高点后,下降的思想。
我们按屏幕的中间的那个子View来划分左右两边的,那么中间那张我始终让他是左边控制的,如果左边上升过程中超过了中间这个View的位置那么他就会成为右边for循环的View。反之在向左滑动的时候,只要超过中间的位置就给左边for循环控制。
public class HorizontalScrollView extends ViewGroup {

/**
 * 每一步我们重点关心每一步新添加的变量。
 */
//2、准备工作
private int windowWidth;
private int halfWindowWidth;//一半的屏幕宽度
private int oneThirdWindowWidth;//三分之一屏幕宽度
//3、重写onLayout排列子View
private int childLeftLeft;//左边第一个的left值
private int childRightLeft;//右边第一个的left值
private int childCount;//子View个数
private int childHalfCount;
private int childViewHeightDifference = 50;//我们把子View的高度差设为50

//4、ViewGroup滑动处理
private int startX;
private int lastX;
private int totalX;

//5、分别让两边子View动起来
private int touchChildCenterCount;//互动时位于屏幕正中间的View
private int leftHigh;
private int rightHigh;

public HorizontalScrollView(Context context) {
    this(context, null);
}

public HorizontalScrollView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}


private void init() {
    WindowManager wm = (WindowManager) getContext()
            .getSystemService(Context.WINDOW_SERVICE);
    windowWidth = wm.getDefaultDisplay().getWidth();//获取屏幕宽度
    halfWindowWidth = windowWidth / 2;
    oneThirdWindowWidth = windowWidth / 3;
}


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    measureChildren(widthMeasureSpec, heightMeasureSpec);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

    childLeftLeft = halfWindowWidth;
    childRightLeft = halfWindowWidth;
    childCount = getChildCount();
    childHalfCount = childCount / 2;
    leftHigh = childViewHeightDifference * (childHalfCount);//初始化左边for循环的最左边View的高度位置
    rightHigh = childViewHeightDifference * (-childHalfCount);//初始化右边for循环的最左边View的高度位置,需要加上i * childViewHeightDifference
    touchChildCenterCount = childHalfCount;
    for (int i = childHalfCount; i >= 0; i--) {
        final View childView = getChildAt(i);
        if (childView.getVisibility() != GONE) {
            final int childWidth = oneThirdWindowWidth;
            childView.layout(childLeftLeft - childWidth / 2, childViewHeightDifference * (childHalfCount - i), childLeftLeft - childWidth / 2 + childWidth,
                    childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());

            childLeftLeft -= childWidth;
        }
    }

    for (int i = childHalfCount + 1; i < childCount; i++) {
        final View childView = getChildAt(i);
        if (childView.getVisibility() != GONE) {
            final int childWidth = oneThirdWindowWidth;
            childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
                    childRightLeft + childWidth / 2 + childWidth, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
            childRightLeft += childWidth;
        }
    }

}

@Override
public boolean onTouchEvent(MotionEvent event) {
    startX = (int) event.getRawX();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:

            break;
        case MotionEvent.ACTION_MOVE:

            int delatX = startX - lastX;

            totalX += delatX;
            scroll();


            break;
        case MotionEvent.ACTION_UP:

            break;
    }
    lastX = startX;
    return true;
}


private void scroll() {

    childLeftLeft = halfWindowWidth;
    childRightLeft = halfWindowWidth;

    if (touchChildCenterCount - childHalfCount >= 1) {
        childLeftLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth;
        childRightLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth;
    }
    if (touchChildCenterCount - childHalfCount <= -1) {
        childLeftLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth;
        childRightLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth;
    }

    for (int i = touchChildCenterCount ; i >= 0; i--) {
        System.out.println("aaaaai:"+i);
        final View childView = getChildAt(i);
        if (childView.getVisibility() != GONE) {
            if (totalX > 0) {
                if (i == touchChildCenterCount ) {
                    final int childWidth = childView.getMeasuredWidth();
                    childView.layout(childLeftLeft - childWidth / 2, rightHigh + i * childViewHeightDifference,
                            childLeftLeft + childWidth / 2 , rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
                } else {
                    final int childWidth = childView.getMeasuredWidth();
                    childView.layout(childLeftLeft - 3 * childWidth / 2, leftHigh - i * childViewHeightDifference,
                            childLeftLeft - childWidth / 2, leftHigh - i * childViewHeightDifference + childView.getMeasuredHeight());
                    childLeftLeft -= childWidth;

                }
            } else {
                final int childWidth = childView.getMeasuredWidth();
                childView.layout(childLeftLeft - childWidth / 2, leftHigh - i * childViewHeightDifference,
                        childLeftLeft + childWidth / 2 , leftHigh - i * childViewHeightDifference + childView.getMeasuredHeight());
                childLeftLeft -= childWidth;
            }


        }

    }
    for (int i = touchChildCenterCount + 1; i < childCount; i++) {
        final View childView = getChildAt(i);
        if (childView.getVisibility() != GONE) {
            final int childWidth = childView.getMeasuredWidth();
            childView.layout(childRightLeft + childWidth / 2, rightHigh + i * childViewHeightDifference,
                    childRightLeft + childWidth / 2 + childWidth, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
            childRightLeft += childWidth;
        }
    }

    //控制滑动范围
    if ((childCount - 3) % 2 == 0) {
        int totalXRange = windowWidth / 3 * (childCount - 3) / 2;
        if (totalX <= -totalXRange) {
            totalX = -totalXRange;

        }
        if (totalX >= totalXRange) {
            totalX = totalXRange;
        }

    } else {
        int totalXRange = (int) (windowWidth / 3 * ((childCount - 3) / 2 + 0.5));

        if (totalX >= totalXRange + windowWidth / 6) {
            totalX = totalXRange + windowWidth / 6;
        }
        if (totalX <= windowWidth / 6 - totalXRange) {
            totalX = windowWidth / 6 - totalXRange;
        }

    }


    if (totalX * oneThirdWindowWidth >= 1 || totalX * oneThirdWindowWidth <= -1) {
        touchChildCenterCount = childHalfCount - totalX /oneThirdWindowWidth;
    }
    if (totalX * oneThirdWindowWidth == 0) {
        touchChildCenterCount = childHalfCount;
    }

    leftHigh = -totalX * childViewHeightDifference / oneThirdWindowWidth + childViewHeightDifference * (childHalfCount);
    rightHigh = totalX * childViewHeightDifference / oneThirdWindowWidth + childViewHeightDifference * (-childHalfCount);

    scrollTo(-totalX, 0);//左右滑动实际上是滑动ViewGroup的内容。

}

}
这里左边for循环的代码比较多也比较复杂,我们先去判断totalX的值是否是大于0的就可以知道第一次滑动的时候用户的手势了,如果用户在开始的时候向右滑动,那么这个中间的View高度就要交给右边的高度控制,这样子就能下降。
大家根据这个思想自己理解一下就差不多了。

7、处理滑动和点击事件冲突和添加VelocityTracker

以下直接贴完整代码
添加了滑动冲突和点击冲突事件的解决,对于点击事件的解决是在onInterceptTouchEvent完成的不过不是很好希望有读者能帮忙给出更好的方法。

public class HorizontalScrollView extends ViewGroup {

    /**
     * 每一步我们重点关心每一步新添加的变量。
     */
    //2、准备工作
    private int windowWidth;
    private int halfWindowWidth;//一半的屏幕宽度
    private int oneThirdWindowWidth;//三分之一屏幕宽度
    //3、重写onLayout排列子View
    private int childLeftLeft;//左边第一个的left值
    private int childRightLeft;//右边第一个的left值
    private int childCount;//子View个数
    private int childHalfCount;
    private int childViewHeightDifference = 50;//我们把子View的高度差设为50

    //4、ViewGroup滑动处理
    private int startX;
    private int lastX;
    private int totalX;

    //5、分别让两边子View动起来
    private int touchChildCenterCount;//互动时位于屏幕正中间的View
    private int leftHigh;
    private int rightHigh;

    //7、处理滑动和点击事件冲突和添加VelocityTracker
    private VelocityTracker mVelocityTracker;
    private int scllorTime;
    private int dispatchEndX;
    private int dispatchEndY;
    private int childMarginLeft = 10;

    public HorizontalScrollView(Context context) {
        this(context, null);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        windowWidth = wm.getDefaultDisplay().getWidth();//获取屏幕宽度
        halfWindowWidth = windowWidth / 2;
        oneThirdWindowWidth = windowWidth / 3;

        mVelocityTracker = VelocityTracker.obtain();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int measuredWidth = 0;
        int measuredHeight = 0;

        final int childCount = getChildCount();
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSizd = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSizd = MeasureSpec.getSize(heightMeasureSpec);

        if (childCount == 0) {
            setMeasuredDimension(0, 0);
        } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measuredWidth, measuredHeight);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(widthSpecSizd, measuredHeight);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            setMeasuredDimension(measuredWidth, heightSpecSizd);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        childCount = getChildCount();
        if (childCount < 3) {
            try {
                throw new Exception("HorizontalScrollView必须有3个或者3个以上的子View");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        childLeftLeft = halfWindowWidth;
        childRightLeft = halfWindowWidth;
        childHalfCount = childCount / 2;
        leftHigh = childViewHeightDifference * (childHalfCount);//初始化左边for循环的最左边View的高度位置
        rightHigh = childViewHeightDifference * (-childHalfCount);//初始化右边for循环的最左边View的高度位置,需要加上i * childViewHeightDifference
        touchChildCenterCount = childHalfCount;
        for (int i = childHalfCount; i >= 0; i--) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = oneThirdWindowWidth;
                childView.layout(childLeftLeft - childWidth / 2, childViewHeightDifference * (childHalfCount - i), childLeftLeft - childWidth / 2 + childWidth - childMarginLeft,
                        childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());

                childLeftLeft -= childWidth;
            }
        }

        for (int i = childHalfCount + 1; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = oneThirdWindowWidth;
                if (i == childCount - 1) {
                    childMarginLeft = 0;
                }
                childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
                        childRightLeft + childWidth / 2 + childWidth - childMarginLeft, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
                childRightLeft += childWidth;
            }
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        startX = (int) event.getRawX();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                break;
            case MotionEvent.ACTION_MOVE:
                int delatX = 0;
                //滑动冲突时不能马上获取到滑动的delatX导致突然是一个很大的值没有想到很好的办法,就只能这样子限制delatX了。
                if (lastX != 0) {
                    delatX = startX - lastX;
                    if (delatX >= 50) {
                        delatX = 50;
                    }
                    if (delatX <= -50) {
                        delatX = -50;
                    }
                }

                totalX += delatX;
                scroll();


                break;
            case MotionEvent.ACTION_UP:


                mVelocityTracker.computeCurrentVelocity(1000);
                float xVelocity = mVelocityTracker.getXVelocity();
                if (Math.abs(xVelocity) >= childViewHeightDifference) {
                    //以下20和300都是我经过多次测试选取的值,感觉运行代码损失了一些时间所以只能测试找出来不能通过计算。
                    if (xVelocity > 0) {
                        scllorTime = 20;
                        int count = (int) (xVelocity / 300);
                        int currentTotalX = totalX + count * windowWidth / 30;
                        postDelayed(new AutoScllorRunnable(currentTotalX), 1);
                    }
                    if (xVelocity < 0) {
                        scllorTime = 20;
                        int count = (int) (Math.abs(xVelocity) / 300);
                        int currentTotalX = totalX - count * windowWidth / 30;
                        postDelayed(new AutoScllorRunnable(currentTotalX), 1);
                    }

                }

                break;
        }
        lastX = startX;
        return true;
    }


    private void scroll() {

        childLeftLeft = halfWindowWidth;
        childRightLeft = halfWindowWidth;

        if (touchChildCenterCount - childHalfCount >= 1) {
            childLeftLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth;
            childRightLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth;
        }
        if (touchChildCenterCount - childHalfCount <= -1) {
            childLeftLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth;
            childRightLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth;
        }

        for (int i = touchChildCenterCount; i >= 0; i--) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                if (totalX > 0) {
                    if (i == touchChildCenterCount) {
                        final int childWidth = childView.getMeasuredWidth();
                        childView.layout(childLeftLeft - childWidth / 2, rightHigh + i * childViewHeightDifference,
                                childLeftLeft + childWidth / 2 - childMarginLeft, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
                    } else {
                        final int childWidth = childView.getMeasuredWidth();
                        childView.layout(childLeftLeft - 3 * childWidth / 2, leftHigh - i * childViewHeightDifference,
                                childLeftLeft - childWidth / 2 - childMarginLeft, leftHigh - i * childViewHeightDifference + childView.getMeasuredHeight());
                        childLeftLeft -= childWidth;

                    }
                } else {
                    final int childWidth = childView.getMeasuredWidth();
                    childView.layout(childLeftLeft - childWidth / 2, leftHigh - i * childViewHeightDifference,
                            childLeftLeft + childWidth / 2 - childMarginLeft, leftHigh - i * childViewHeightDifference + childView.getMeasuredHeight());
                    childLeftLeft -= childWidth;
                }


            }

        }
        for (int i = touchChildCenterCount + 1; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                final int childWidth = childView.getMeasuredWidth();
                if (i == childCount - 1) {
                    childMarginLeft = 0;
                }
                childView.layout(childRightLeft + childWidth / 2, rightHigh + i * childViewHeightDifference,
                        childRightLeft + childWidth / 2 + childWidth - childMarginLeft, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
                childRightLeft += childWidth;
            }
        }

        //控制滑动范围
        if ((childCount - 3) % 2 == 0) {
            int totalXRange = windowWidth / 3 * (childCount - 3) / 2;
            if (totalX <= -totalXRange) {
                totalX = -totalXRange;

            }
            if (totalX >= totalXRange) {
                totalX = totalXRange;
            }

        } else {
            int totalXRange = (int) (windowWidth / 3 * ((childCount - 3) / 2 + 0.5));

            if (totalX >= totalXRange + windowWidth / 6) {
                totalX = totalXRange + windowWidth / 6;
            }
            if (totalX <= windowWidth / 6 - totalXRange) {
                totalX = windowWidth / 6 - totalXRange;
            }

        }


        if (totalX * oneThirdWindowWidth >= 1 || totalX * oneThirdWindowWidth <= -1) {
            touchChildCenterCount = childHalfCount - totalX / oneThirdWindowWidth;
        }
        if (totalX * oneThirdWindowWidth == 0) {
            touchChildCenterCount = childHalfCount;
        }

        leftHigh = -totalX * childViewHeightDifference / oneThirdWindowWidth + childViewHeightDifference * (childHalfCount);
        rightHigh = totalX * childViewHeightDifference / oneThirdWindowWidth + childViewHeightDifference * (-childHalfCount);

        scrollTo(-totalX, 0);//左右滑动实际上是滑动ViewGroup的内容。

    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //打开注释就可以在这个控件得到上下滑动的事件,不过我们这个控件的需求基本是左右滑动打开的话会影响体验。
        int dispatchStartX = (int) ev.getX();
        int dispatchStartY = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int deltax = dispatchEndX - dispatchStartX;
                int deltaY = dispatchEndY - dispatchStartY;
                if (Math.abs(deltaY) <= Math.abs(deltax)) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                } else {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        dispatchEndX = dispatchStartX;
        dispatchEndY = dispatchStartY;
        return super.dispatchTouchEvent(ev);
    }

    int InterceptEndX;
    int InterceptEndY;
    boolean intercept;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int dispatchStartX = (int) ev.getX();
        int dispatchStartY = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                int deltax = InterceptEndX - dispatchStartX;
                int deltaY = InterceptEndY - dispatchStartY;
                if (Math.abs(deltaY) <= Math.abs(deltax) - 10) {
                    startX = 0;
                    lastX = 0;
                    intercept = true;
                } else {
                    intercept = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        InterceptEndX = dispatchStartX;
        InterceptEndY = dispatchStartY;
        return intercept;
    }

    private class AutoScllorRunnable implements Runnable {
        int currentTotalx;
        int totalXValue;

        public AutoScllorRunnable(int currentTotalx) {
            this.currentTotalx = currentTotalx;
            totalXValue = (currentTotalx - totalX) / 10;
        }

        @Override
        public void run() {
            if (scllorTime > 0) {
                totalX = totalX + totalXValue;
                scllorTime--;
                scroll();
                HorizontalScrollView.this.postDelayed(this, 1);
            }
        }
    }
}

项目已经上传到github
随手给个star吧。谢谢。

https://github.com/laotan7237/HorizontalScrollView

如果有发现作者哪里写的有问题或者有更好的翻案欢迎指出,可以通过QQ联系作者502325525.

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值