2018.03.15、View 繪製流程學習 筆記

Traning發表於2018-03-15

一、View的測量過程

1、MeasureSpace :封裝了父View傳遞到子View的佈局要求。是由size(大小) 和modle(模式組成),有三種模式:

  • UNSPECIFIED:

父檢視不對子檢視施加任何限制,子檢視可以得到任意想要的大小;

  • EXACTLY:

父檢視希望子檢視的大小是specSize中指定的大小;

  • AT_MOST:

子檢視的大小最多是specSize中的大小。

2、measure(int widthMeasureSpec, int heightMeasureSpec):當父View對子view進行測量時,會呼叫的方法。兩個入參分別代表對子View的限制。

  • 1)先判斷是否需要進行測量: View.measure()方法時View並不是立即就去測量,而是先判斷一下是否有必要進行測量操作,如果不是強制測量或者MeasureSpec與上次的MeasureSpec相同的時候,那麼View就不需要重新測量了.

  • 2)從快取中讀取是否測量過: 如果不滿足上面條件,View就考慮去做測量工作了.但在測量之前,View還想偷懶,如果能在快取中找到上次的測量結果,那直接從快取中獲取就可以了。它會以MeasureSpec計算出的key值作為鍵,去成員變數mMeasureCache中查詢是否快取過對應key的測量結果,

  • 3)onMeasure()方法進行測量: 如果不能從mMeasureCache中讀到快取過的測量結果,只能乖乖地呼叫onMeasure()方法去完成實際的測量工作,並且將尺寸限制條件widthMeasureSpec和heightMeasureSpec傳遞給onMeasure()方法。

  • 4)儲存測量結果: 最終View都會得到測量的結果,並且將結果儲存到mMeasuredWidth和mMeasuredHeight這兩個成員變數中,同時快取到成員變數mMeasureCache中,以便下次執行measure()方法時能夠從其中讀取快取值

3、onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
複製程式碼

4、setMeasuredDimension()

  • 1)通過setMeasuredDimensionRaw() ,把測量完的寬高值賦值給mMeasuredWidth、mMeasuredHeight

二、

1、ViewGroup怎麼知道他的子View是多大呢?View提供了以下三組方法:

  • getMeasuredWidth()和getMeasuredHeight()
  • getMeasuredWidthAndState()和getMeasuredHeightAndState()
  • getMeasuredState()

2、mMeasuredWidth是一個Int型別的值,其是由4個位元組組成的。

  • 其高位的第一個位元組為第一部分,用於標記測量完的尺寸是不是達到了View想要的寬度,我們稱該資訊為測量的state資訊。
  • 其低位的三個位元組為第二部分,用於儲存測量到的寬度。
  • 有點類似於measureSpec

3、resolveSizeAndState()

  • 1)這個方法的程式碼結構跟前文提到的getDefaultSize()方法很相似。

三、ViewGroup的measure過程

1、對於ViewGroup來說,除了完成自己的measure過程,還會遍歷去呼叫所有子元素的measure()方法,各個子元素再遞迴去執行這個過程

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            //遍歷每個子元素,如果該子元素不是GONE的話,就去測量該子元素
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
複製程式碼

2、對每個子view 再次進行測量

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        //獲取child自身的LayoutParams屬性
        final LayoutParams lp = child.getLayoutParams();
        //根據父佈局的MeasureSpec,父佈局的padding和child的LayoutParams這三個引數,通過getChildMeasureSpec()方法計算出子元素的MeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
        //呼叫measure()方法測量child,前文已經解釋過這個方法,
        //呼叫該方法之後會將view的寬高值儲存在mMeasuredWidth和mMeasuredHeight這兩個屬性當中,這樣child的尺寸就已經測量出來了
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
複製程式碼

measureChild()的思想就是取出子元素的LayoutParams,然後再通過getChildMeasureSpec()方法來建立子元素的MeasureSpec,接著將MeasureSpec傳給View的measure()方法來完成對子元素的測量。

3、getChildMeasureSpec(int spec, int padding, int childDimension)

  • getChildMeasureSpec()這個方法清楚展示了普通View的MeasureSpec的建立規則,每個View的MeasureSpec狀態量由其直接父View的MeasureSpec和View自身的屬性LayoutParams(LayoutParams有寬高尺寸值等資訊)共同決定。

4、MeasureSpec狀態後,將其與尺寸值通過makeMeasureSpec(int size,int mode)方法結合在一起,就是最終傳給View的onMeasure(int, int)方法的MeasureSpec值了。

5、ViewRootImpl是連線WindowManager和DecorView的紐帶,控制元件的測量、佈局、繪製以及輸入事件的分發處理都由ViewRootImpl觸發。
performTraversals()它呼叫了一個performTraversals()方法使得View樹開始三大工作流程

private void performTraversals() {
            ...
        if (!mStopped || mReportNextDraw) {
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    
            ...
            }
        } 

        if (didLayout) {
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
            ...
        }


        if (!cancelDraw && !newSurface) {
            performDraw();
        }
        ...
}

複製程式碼

6、performTraversals() 通過呼叫performMeasure()、performLayout()、performDraw()這三個方法,這三個方法分別完成DecorView的measure、layout、和draw這三大流程,其中performMeasure()中會呼叫measure()方法,在measure()方法中又會呼叫onMeasure()方法,在onMeasure()方法中會對所有子元素進行measure過程,這個時候measure流程就從父容器傳遞到子元素中了,這樣就完成了一次measure過程。

7、樹是遍歷順序,這意味著父View將被先繪製,子View檢視後被繪製。

8、在measure過程中,通過 widthMeasureSpec() heightMeasureSpec 以及View自身的LayoutParams共同決定子檢視的測量規格;

  • 1)MeasureSpec :父View對子View的測量模式、測量大小的要求。

  • 2)子View 檢視通過LayoutParams,這個類並告訴父View 檢視他們應該怎樣被測量和放置。

  • 3)它的尺寸可以有三種表示方法:

    1、具體數值 2、FILL_PARENT 3、WRAP_CONTENT

9、對於不同的ViewGroup的子類,有著各自不同的LayoutParams。

10、getMeasuredWidth()、getMeasuredHeight()返回的是measure過程得到的mMeasuredWidth和mMeasuredHeight的值,而getWidth()和getHeight()返回的是mRight - mLeft和mBottom - mTop的值。

public final int getMeasuredWidth() {  
        return mMeasuredWidth & MEASURED_SIZE_MASK;  
    }  
public final int getWidth() {  
        return mRight - mLeft;  
    }  
複製程式碼

11、draw方法步驟:繪製背景---------繪製自己--------繪製chrildren----繪製裝飾。

相關文章