View 的measure 和onMeasure


最近有人問了我關於measure 和 onMeasure的一些問題,什么時候調用measure方法, 兩者的區別,什么時候重寫onMeasure方法。其實網上有很多人寫過這方面的博客。我覺得不要因為網上有了,就不寫。看懂是一回事,講出來是一回事,寫出來又是另外一回事。看了東西還是別人了,只有通過寫或是講出來才能更深刻的理解。

我們先看下什么時候會調用 measure方法:

講到view的繪制原理上,肯定都會提到ViewRootImpl類,該類是activity 的view 樹與Window的中間通信者。它里面的mView對象指向的是DecorView。ViewRootImpl類是View繪制過程(測量,布局,繪制)的起點。那么measure方法是怎么進行的呢?

我們先看下調用順序:

通過上面的調用順序,我們可以看到measure是在什么時候調用,在View里,有個mParent的變量。這個變量其實就是ViewRootImpl. 所以在調用View的requestFitSystemWindows, requestLayout, invalidateChildInParent時候,都會調用measure方法。

那measure方法到底是干什么用的。其實它主要是用於計算並獲取該View的實際大小,也就是mMeasuredWidth和mMeasuredHeight兩個值。

1.首先判斷是否有設置為opticalBounds的屬性,如果當前的opticalBounds的屬性和父類的不一致,就重新計算wdithMeasureSpec和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);
        }

 

2. 如果有窗口移動或是reSize的操作,導致的強制刷新,或是view的寬度或是長度有變化時候。如果是不用measure cache,那么需要調用onMeasure方法進行進行view的width和height。如果有measure cache,那么只需要拿到measure cache里的值進行更新就可以了。

 if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            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
                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;
        }

 

3.最后保存當前的值到measure cache里和重新記錄old值,key值是用 widthMeasureSpec和heightMeasureSpec拼湊的64位數

 // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

      // ......省略.... 
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension

 

View里的onMeasure 做得事情很簡單,就是根據spec來計算具體的值。當然了如果是RelativeLayout等ViewGroup里的onMeasure就會相當復雜。

 

那在上面時候需要重寫onMeasure方法呢?

一般是需要自己去定義高寬規則的時候,比如需要顯示一個特定高度的listView。不管規則怎么變,在重新計算高寬后,一定要調用setMeasuredDimension


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM