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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章