Android 简单的自定义View继承ViewGroup代码学习分享(一)

注明:该实例取自Android开发艺术探索

在这里记录一下自己学习过程中遇到的一些问题与大家分享,也方便自己以后查阅,水平有限,欢迎批评指正。

请看一下运行效果

下面是核心代码实现,其中的笔记是我测试过程中遇到的一些问题

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //不调用 super.OnMeasure 会报下面的错误!!
        //java.lang.IllegalStateException: View with id 2131165228: com.lollo.android.demoactivity_test2.
        //ui.HorizontalScrollViewEx#onMeasure() did not set the measured dimension by calling setMeasuredDimension()
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.d(TAG,"onMeasure");
        int measuredWidth = 0;
        int measuredHeight = 0;
        final int childCount = getChildCount();
        //调用下面这句之后,widthMeasureSpec、heightMeasureSpec的值不会改变
        //但是子View 通过getMeasuredHeight()、getMeasuredWidth()可以获取测量高/宽
        //下面这句就是测量子View的,测量后的值会保存在子View的MeasureSpec内
        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) {
            setMeasuredDimension(0, 0);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            setMeasuredDimension(measuredWidth, heightSpaceSize);
        } else {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measuredWidth, measuredHeight);
        }
    }

下面是实现滑动效果以及解决滑动冲突的核心代码

水平滑动时拦截事件

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {//通过 Log看 只有 ACTION_DOWN 事件会走到这里
                                                             //之后找到原因是因为没有重写 onMeasure()方法
        //Log.d(TAG, "onInterceptTouchEvent action=" + event.getAction());//一直打印 0是因为没有重写 onMeasure()方法
        boolean intercepted = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {//0
                intercepted = false;
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                    intercepted = true;
                }
                //Log.d(TAG, "ACTION_DOWN intercepted=" + intercepted);
                break;
            }
            case MotionEvent.ACTION_MOVE: {//2
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
                //如果屏蔽以下代码将无法实现左右滑动
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }

                //Log.d(TAG, "ACTION_MOVE intercepted=" + intercepted);
                break;
            }
            case MotionEvent.ACTION_UP: {//1
                //Log.d(TAG, "ACTION_UP intercepted=" + intercepted);
                intercepted = false;
                break;
            }
            default:
                break;
        }
        //Log.d(TAG, "after onInterceptTouchEvent intercepted=" + intercepted);
        mLastXIntercept = x;
        mLastYIntercept = y;
        //注意下面两句不能漏掉
        //首先执行的onInterceptTouchEvent,然后是onTouchEvent
        //没有下面两句执行onTouchEvent的时候得到的坐标就不是最新的
        mLastX = x;
        mLastY = y;
        return intercepted;
    }

重写onTouchEvent

@Override
    public boolean onTouchEvent(MotionEvent event) {
        //如果上面方法不拦截,返回false 将不会走这里
        //而是调用子View的 onTouchEvent 方法
        //Log.d(TAG,"onTouchEvent MotionEvent action:"+event.getAction());
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:/* 0 */ {
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                break;
            }
            case MotionEvent.ACTION_MOVE:/* 2 */ {//当用户左右滑动并且onInterceptTouchEvent返回true时才会走到这里
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                //当手往左滑动时(相当于往X轴反方向滑动),x坐标值会越来越小 deltaX为负值
                //通过scrollBy 之后得到的滑动的目的坐标为mScrollX+deltaX(mScrollX初始值为0)
                //mScrollX初始值为0,但是mLastX的值在ACTION_MOVE事件这里不一定为0,它应该在
                //ACTION_DOWN事件发生时获取一个初始值,而ACTION_DOWN事件发生一定会走onInterceptTouchEvent
                //因为当ACTION_DOWN事件发生时 ViewGroup 总是会调用自己的onInterceptTouchEvent方法
                //最终mScrollX为负值(就是deltaX的值)
                scrollBy(-deltaX, 0);
                //Log.d(TAG,"onTouchEvent ACTION_move deltaX:"+deltaX);
                break;
            }

            case MotionEvent.ACTION_UP: /* 1 */{
                //scrollX表示View左边缘到 View内容左边缘的距离(这里指第一个ListView左边缘)
                //如果当前显示的是第二个ListView那么View内容左边缘我们是看不到的它和View左边缘
                //刚好隔了一个屏幕的距离(1080)(前提是一个ListView宽度是match_parent且是屏幕宽度)此时scrollX为正值
                int scrollX = getScrollX();
                //int scrollToChildIndex = scrollX / mChildWidth;
                mVelocityTracker.computeCurrentVelocity(1000);
                float xVelocity = mVelocityTracker.getXVelocity();
                Log.d(TAG,"mChildIndex:"+mChildIndex+" scrollX:"+scrollX);

                if (Math.abs(xVelocity) >= 50) {//滑动方向往左时 xVelocity 为负值
                    mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
                } else {//如果 scrollX 大于子View的一半宽度 mChildIndex 为 1 ,否则为 0
                    mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
                }
                Log.d(TAG,"mChildIndex:"+mChildIndex+" xVelocity:"+xVelocity);

                //mChildIndex最小值为 0,最大不能超过 mChildrenSize-1
                mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
                int dx = mChildIndex * mChildWidth - scrollX;
                Log.d(TAG,"mChildIndex:" + mChildIndex+" dx:"+dx);
                smoothScrollBy(dx, 0);
                mVelocityTracker.clear();
                break;
            }
            default:
                break;
        }
        mLastX = x;
        mLastY = y;
        return true;
    }

代码下载地址:

https://download.csdn.net/download/lollo01/12102079

 

你可能感兴趣的:(Android自定义View)