[Android FrameWork 6.0源碼學習] View的重繪過程之Measure,androidmeasure

來源:互聯網
上載者:User

[Android FrameWork 6.0源碼學習] View的重繪過程之Measure,androidmeasure

View繪製的三部曲,  測量,布局,繪畫
今天我們分析測量過程

view的測量是從ViewRootImpl發起的,View需要重繪,都是發送請求給ViewRootImpl,然後他組織重繪
在重繪的過程中,有一步就是測量,通過代碼來分析測量過程

    private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {        int childWidthMeasureSpec;        int childHeightMeasureSpec;        boolean windowSizeMayChange = false;        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG,                "Measuring " + host + " in display " + desiredWindowWidth                + "x" + desiredWindowHeight + "...");            boolean goodMeasure = false;        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {            // On large screens, we don't want to allow dialogs to just            // stretch to fill the entire width of the screen to display            // one line of text.  First try doing the layout at a smaller            // size to see if it will fit.            final DisplayMetrics packageMetrics = res.getDisplayMetrics();            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);            int baseSize = 0;            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {                baseSize = (int)mTmpValue.getDimension(packageMetrics);            }            if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize);            if (baseSize != 0 && desiredWindowWidth > baseSize) {                //擷取測量的規格,是一個32位的位元值,前兩位標識mode,後30位表示view的長/寬                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);                //向DecorView發起重繪                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {                    goodMeasure = true;                } else {                    // Didn't fit in that size... try expanding a bit.                    baseSize = (baseSize+desiredWindowWidth)/2;                    if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize="                            + baseSize);                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                    if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {                        if (DEBUG_DIALOG) Log.v(TAG, "Good!");                        goodMeasure = true;                    }                }            }        }        if (!goodMeasure) {            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {                windowSizeMayChange = true;            }        }        if (DBG) {            System.out.println("======================================");            System.out.println("performTraversals -- after measure");            host.debug();        }        return windowSizeMayChange;    }

這個函數通過getRootMeasureSpec方法,擷取測量規格,然後調用performMeasure方法開始分發給整個的view樹。

 

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {        int measureSpec;        switch (rootDimension) {        case ViewGroup.LayoutParams.MATCH_PARENT:            // Window can't resize. Force root view to be windowSize.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);            break;        case ViewGroup.LayoutParams.WRAP_CONTENT:            // Window can resize. Set max size for root view.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);            break;        default:            // Window wants to be an exact size. Force root view to be that size.            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);            break;        }        return measureSpec;    }

 

 通過MeasureSpec的makeMeasureSpec方法來產生測量規格,先判斷出布局是 match_parent 或者是 wrap_content,或者是確定的數值
 然後把windowSize傳遞下去。

    public static int makeMeasureSpec(int size, int mode) {            if (sUseBrokenMakeMeasureSpec) {                return size + mode;            } else {                return (size & ~MODE_MASK) | (mode & MODE_MASK);            }    }

 

 API大於17的都會走else判斷,這塊我分析一下計算結果。有助於理解後邊的運算

 makeMeasureSpec的運算結果是一個32位的位元值,前2位表示測量的規格 EXACTLY/AT_most 後30位表示 windowSize,舉個運算例子    
 size=320,mode=EXACTLY,換算成二進位就是下邊的兩串值
 size=0000 0000 0000 0000 0000 0001 0100 0000
 mode=0100 0000 0000 0000 0000 0000 0000 0000
 mask=1100 0000 0000 0000 0000 0000 0000 0000
 最後用與運算整合size和mode
 0000 0000 0000 0000 0000 0001 0100 0000 |
 0100 0000 0000 0000 0000 0000 0000 0000 =
 0100 0000 0000 0000 0000 0001 0100 0000

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");        try {            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);        } finally {            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }    }

 

 這個mView就是我們在window類中組合出來的DecorView,這個方法調用了view的measure方法,measure會調用OnMeasure方法,然後就實現了整個view樹的測量工作

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        boolean optical = isLayoutModeOptical(this);        if (optical != isLayoutModeOptical(mParent)) {            Insets insets = getOpticalInsets();            int oWidth  = insets.left + insets.right;            int oHeight = insets.top  + insets.bottom;            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);        }        // 這塊又把寬的測量規格和高的測量規格拼接在了一起,作為緩衝中的key        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||                widthMeasureSpec != mOldWidthMeasureSpec ||                heightMeasureSpec != mOldHeightMeasureSpec) {            // first clears the measured dimension flag            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;            resolveRtlPropertiesIfNeeded();            //判斷是否強制測量,如果強制就重新調用onMeasure,整個view樹重新測量,否則就從緩衝中得到上次的測量規格,            //因為DecorView是FrameLayout的子類,所以onMeasure就是調用FrameLayout的onMeasure方法            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :                    mMeasureCache.indexOfKey(key);            if (cacheIndex < 0 || sIgnoreMeasureCache) {                // measure ourselves, this should set the measured dimension flag back                onMeasure(widthMeasureSpec, heightMeasureSpec);                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;            } else {                long value = mMeasureCache.valueAt(cacheIndex);                // Casting a long to int drops the high 32 bits, no mask needed                //調用完這個方法之後,getMeasuredWidth和getMeasuredHeight就可以取到值了                setMeasuredDimensionRaw((int) (value >> 32), (int) value);                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;            }            // flag not set, setMeasuredDimension() was not invoked, we raise            // an exception to warn the developer            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {                throw new IllegalStateException("View with id " + getId() + ": "                        + getClass().getName() + "#onMeasure() did not set the"                        + " measured dimension by calling"                        + " setMeasuredDimension()");            }            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;        }        mOldWidthMeasureSpec = widthMeasureSpec;        mOldHeightMeasureSpec = heightMeasureSpec;        //在按照固定的格式,把本次的測量規格put到集合中        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension    }

 

這個方法做的功能大概就是這樣,先判斷是否有緩衝,沒緩衝就測量一下整個樹,然後按照固定的格式在存入緩衝中

從這裡開始,我們的整個測量過程就開始跑起來了

     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int count = getChildCount();        //只有match_parent和固定寬高 的mode才是EXACTLY,wrap_content是AT_MOST        final boolean measureMatchParentChildren =                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;        mMatchParentChildren.clear();        int maxHeight = 0;  //包含padding的高度        int maxWidth = 0;   //包含width的寬度        int childState = 0; //前16位是寬的測量mode,後16位是高的測量mode        //輪尋測量所有的child        for (int i = 0; i < count; i++) {            final View child = getChildAt(i);            //只要是不為GONE,就會進入測量範圍            if (mMeasureAllChildren || child.getVisibility() != GONE) {                //測量child的時候,減去padding加上margin的距離,得到child的規格後回調用child的measure,然後在重新走上邊分析的流程,調用child的onMeasure                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);                //賦值maxWidth和maxHeight,之後就可以用getHeight和getWidth方法了                final LayoutParams lp = (LayoutParams) child.getLayoutParams();                maxWidth = Math.max(maxWidth,                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);                maxHeight = Math.max(maxHeight,                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);                childState = combineMeasuredStates(childState, child.getMeasuredState());                if (measureMatchParentChildren) {                    if (lp.width == LayoutParams.MATCH_PARENT ||                            lp.height == LayoutParams.MATCH_PARENT) {                        mMatchParentChildren.add(child);                    }                }            }        }        // Account for padding too        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();        // Check against our minimum height and width        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());        // Check against our foreground's minimum height and width        final Drawable drawable = getForeground();        if (drawable != null) {            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());        }        //設定子空間的測量結果        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),                resolveSizeAndState(maxHeight, heightMeasureSpec,                        childState << MEASURED_HEIGHT_STATE_SHIFT));        //開始測量寬高都是MATCH_PARENT的布局        count = mMatchParentChildren.size();        if (count > 1) {            for (int i = 0; i < count; i++) {                final View child = mMatchParentChildren.get(i);                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();                final int childWidthMeasureSpec;                if (lp.width == LayoutParams.MATCH_PARENT) {                    final int width = Math.max(0, getMeasuredWidth()                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()                            - lp.leftMargin - lp.rightMargin);                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(                            width, MeasureSpec.EXACTLY);                } else {                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +                            lp.leftMargin + lp.rightMargin,                            lp.width);                }                final int childHeightMeasureSpec;                if (lp.height == LayoutParams.MATCH_PARENT) {                    final int height = Math.max(0, getMeasuredHeight()                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()                            - lp.topMargin - lp.bottomMargin);                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                            height, MeasureSpec.EXACTLY);                } else {                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +                            lp.topMargin + lp.bottomMargin,                            lp.height);                }                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);            }        }    }

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.