自定义ViewAndroid 自定义view

Android-自定义ViewGroup(一) 水平滑动

2017-02-22  本文已影响0人  _SHYII

1.重新测量、布局

继承ViewGroup重写onMeasure和onLayout方法

1)在onMeasure中计算childVIew的测量值及模式,并设置自己的宽高

测量子View:

方法1:调用 measureChildren(widthMeasureSpec, heightMeasureSpec);
方法2:遍历childVIew调用
measureChild(childVIew, widthMeasureSpec, heightMeasureSpec);

设置自身ViewGro宽高:

设置自定义的控件MyViewGroup的大小,如果是MeasureSpec.EXACTLY则直接使用父ViewGroup传入的宽和高,否则设置为自己计算的宽和高
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width, heightMode == MeasureSpec.EXACTLY ? heightSize : height);

2)在onLayout中为每个子View设置布局,可以获取每个子View并分别得到子View坐标,调用child.layout(left, top,right , bottom)为子View设置布局位置。

2.滑动事件处理

3)重写onInterceptTouchEvent,在MotionEvent.ACTION_MOVE事件中通过水平、竖直方向位移大小判断,并对水平方向事件进行拦截(返回true为拦截,由自己处理)
if (Math.abs(deltaX) > Math.abs(deltaY)) {return true;}
4)重写onTouchEvent,在MotionEvent.ACTION_MOVE调用scrollBy(-deltaX, 0)实现左右滑动;在MotionEvent.ACTION_UP用scroller.startScroll(getScrollX(), 0, dx, 0, 500)限定ViewGroup左右边界

下面贴上代码,结合代码看便于理解:

public class ZhanfHorizontalScrollview extends ViewGroup {
    private int childCount;//子View数量
    private int childIndex;//子View索引
    private int measuredHeight;//子View的高度
    private int measuredWidth;//子View的宽度
    private Scroller scroller;//弹性滑动对象,用于实现View的弹性滑动
    private VelocityTracker velocityTracker;//速度追踪,

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

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

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


    private void init() {
        scroller = new Scroller(getContext());
        velocityTracker = VelocityTracker.obtain();
    }

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

        // 获得它的父容器为它设置的测量模式和大小
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        // 1.计算自定义的ViewGroup中所有子控件的大小
        // measureChildren(widthMeasureSpec, heightMeasureSpec);
        int height = 0;
        int width = 0;

        if (childCount > 0) {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (child != null && child.getVisibility() != GONE) {
                    // 2.计算自定义的ViewGroup中所有子控件的大小
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);
                    measuredHeight = child.getMeasuredHeight();
                    measuredWidth = child.getMeasuredWidth();
                    height = Math.max(height, measuredHeight);
                    width += measuredWidth;
                }
            }
        }
        // 设置自定义的控件MyViewGroup的大小,如果是MeasureSpec.EXACTLY则直接使用父ViewGroup传入的宽和高,否则设置为自己计算的宽和高
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width, heightMode == MeasureSpec.EXACTLY ? heightSize : height);

    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        childCount = getChildCount();
        int height = 0;
        int width = 0;
        if (childCount > 0) {
            for (int index = 0; index < childCount; index++) {
                View child = getChildAt(index);
                if (child.getVisibility() != GONE && child != null) {
                    measuredHeight = getMeasuredHeight();
                    measuredWidth = getMeasuredWidth();
                    height = Math.max(height, measuredHeight);
                    child.layout(width, 0, width + measuredWidth, height);
                }
                width += measuredWidth;
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        velocityTracker.addMovement(event);

        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //如果动画还没有结束,再次点击时结束上次动画,即开启这次新的ACTION_DOWN的动画
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                scrollBy(-deltaX, 0);
                break;
            case MotionEvent.ACTION_UP:
                int scrollX = getScrollX();//View的左边缘 - View内容的左边缘 位置的像素点
                // int scrollToChildIndex = scrollX / measuredWidth;
                velocityTracker.computeCurrentVelocity(1000);
                float xVelocity = velocityTracker.getXVelocity();//获取X方向手指滑动的速度,之前必须调用computeCurrentVelocity()方法
                if (Math.abs(xVelocity) > 200) {//当滑动速度>200Px/S时
                    childIndex = xVelocity > 0 ? childIndex - 1 : childIndex + 1;
                } else {
                    childIndex = (scrollX + measuredWidth / 2) / measuredWidth;
                }
                childIndex = Math.max(0, Math.min(childIndex, childCount - 1));//限定childIndex在0到childCount之间
                int dx = childIndex * measuredWidth - scrollX;
                scroller.startScroll(getScrollX(), 0, dx, 0, 500);//up 时自动滚动到
                invalidate();
                break;
        }
        mLastX = x;
        mLastY = y;
        return true;
    }

    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }

    //分别记录上次滑动的坐标
    private int mLastX = 0;
    private int mLastY = 0;

    //分别记录上次滑动的坐标(onINterceptTouchEvent)
    private int mLastXIntercept = 0;
    private int mLastYIntercept = 0;


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        mLastX = x;
        mLastY = y;
        mLastXIntercept = x;
        mLastYIntercept = y;

        return super.onInterceptTouchEvent(ev);
    }
}

好了,有关水平滑动的实现就到这里。记住,自定义ViewGroup无非就是对子VIew进行逐个measure、layout,再进行相应情况的事件拦截、处理的过程,细细做下来也没那么难

参考:
《Android开发艺术探索》——3.5View的滑动冲突
Android 手把手教您自定义ViewGroup(一)

上一篇 下一篇

猜你喜欢

热点阅读