Android面試收集錄12 View測量、布局及繪製原理

來源:互聯網
上載者:User

標籤:led   而在   ...   height   位置   params   步驟   c51   尺寸   

 

一、View繪製的流程架構

View的繪製是從上往下一層層迭代下來的。DecorView-->ViewGroup(--->ViewGroup)-->View ,按照這個流程從上往下,依次measure(測量),layout(布局),draw(繪製)。

 

 

 

二、Measure流程

顧名思義,就是測量每個控制項的大小。

調用measure()方法,進行一些邏輯處理,然後調用onMeasure()方法,在其中調用setMeasuredDimension()設定View的寬高資訊,完成View的測量操作。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {}

measure()方法中,傳入了兩個參數 widthMeasureSpec, heightMeasureSpec 表示View的寬高的一些資訊。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }

由上述流程來看Measure流程很簡單,關鍵點是在於widthMeasureSpec, heightMeasureSpec這兩個參數資訊怎麼獲得?

如果有了widthMeasureSpec, heightMeasureSpec,通過一定的處理(可以重寫,自訂處理步驟),從中擷取View的寬/高,調用setMeasuredDimension()方法,指定View的寬高,完成測量工作。

MeasureSpec的確定

先介紹下什麼是MeasureSpec?

MeasureSpec由兩部分組成,一部分是測量模式,另一部分是測量的尺寸大小。

其中,Mode模式共分為三類

UNSPECIFIED :不對View進行任何限制,要多大給多大,一般用於系統內部

EXACTLY:對應LayoutParams中的match_parent和具體數值這兩種模式。檢測到View所需要的精確大小,這時候View的最終大小就是SpecSize所指定的值,

AT_MOST :對應LayoutParams中的wrap_content。View的大小不能大於父容器的大小。

那麼MeasureSpec又是如何確定的?

對於DecorView,其確定是通過螢幕的大小,和自身的布局參數LayoutParams。

這部分很簡單,根據LayoutParams的布局格式(match_parent,wrap_content或指定大小),將自身大小,和螢幕大小相比,設定一個不超過螢幕大小的寬高,以及對應模式。

對於其他View(包括ViewGroup),其確定是通過父布局的MeasureSpec和自身的布局參數LayoutParams。

這部分比較複雜。以下列圖表表示不同的情況:

當子View的LayoutParams的布局格式是wrap_content,可以看到子View的大小是父View的剩餘尺寸,和設定成match_parent時,子View的大小沒有區別。為了顯示區別,一般在自訂View時,需要重寫onMeasure方法,處理wrap_content時的情況,進行特別指定。

從這裡看出MeasureSpec的指定也是從頂層布局開始一層層往下去,父布局影響子布局。

可能關於MeasureSpec如何確定View大小還有些模糊,篇幅有限,沒詳細具體展開介紹,可以看這篇文章

View的測量流程:

 

 

 

三、Layout流程

測量完View大小後,就需要將View布局在Window中,View的布局主要通過確定上下左右四個點來確定的。

其中布局也是自上而下,不同的是ViewGroup先在layout()中確定自己的布局,然後在onLayout()方法中再調用子View的layout()方法,讓子View布局。在Measure過程中,ViewGroup一般是先測量子View的大小,然後再確定自身的大小。

public void layout(int l, int t, int r, int b) {      // 當前視圖的四個頂點    int oldL = mLeft;      int oldT = mTop;      int oldB = mBottom;      int oldR = mRight;      // setFrame() / setOpticalFrame():確定View自身的位置    // 即初始化四個頂點的值,然後判斷當前View大小和位置是否發生了變化並返回   boolean changed = isLayoutModeOptical(mParent) ?            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);    //如果視圖的大小和位置發生變化,會調用onLayout()    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {          // onLayout():確定該View所有的子View在父容器的位置             onLayout(changed, l, t, r, b);        ...}

上面看出通過 setFrame() / setOpticalFrame():確定View自身的位置,通過onLayout()確定子View的布局。 setOpticalFrame()內部也是調用了setFrame(),所以具體看setFrame()怎麼確定自身的位置布局。

protected boolean setFrame(int left, int top, int right, int bottom) {    ...// 通過以下指派陳述式記錄下了視圖的位置資訊,即確定View的四個頂點// 即確定了視圖的位置    mLeft = left;    mTop = top;    mRight = right;    mBottom = bottom;    mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);}

確定了自身的位置後,就要通過onLayout()確定子View的布局。onLayout()是一個可繼承的空方法。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {    }

如果當前View就是一個單一的View,那麼沒有子View,就不需要實現該方法。

如果當前View是一個ViewGroup,就需要實現onLayout方法,該方法的實現個自訂ViewGroup時其特性有關,必須自己實現。

由此便完成了一層層的的布局工作。

View的布局流程:

四、Draw過程

View的繪製過程遵循如下幾步:

①繪製背景 background.draw(canvas)

②繪製自己(onDraw)

③繪製Children(dispatchDraw)

④繪製裝飾(onDrawScrollBars)

從源碼中可以清楚地看出繪製的順序。

public void draw(Canvas canvas) {// 所有的視圖最終都是調用 View 的 draw ()繪製視圖( ViewGroup 沒有複寫此方法)// 在自訂View時,不應該複寫該方法,而是複寫 onDraw(Canvas) 方法進行繪製。// 如果自訂的視圖確實要複寫該方法,那麼需要先調用 super.draw(canvas)完成系統的繪製,然後再進行自訂的繪製。    ...    int saveCount;    if (!dirtyOpaque) {          // 步驟1: 繪製本身View背景        drawBackground(canvas);    }        // 如果有必要,就儲存圖層(還有一個複原圖層)        // 最佳化技巧:        // 當不需要繪製 Layer 時,“儲存圖層“和“複原圖層“這兩步會跳過        // 因此在繪製的時候,節省 layer 可以提高繪製效率        final int viewFlags = mViewFlags;        if (!verticalEdges && !horizontalEdges) {        if (!dirtyOpaque)              // 步驟2:繪製本身View內容  預設為空白實現,  自訂View時需要進行複寫            onDraw(canvas);        ......        // 步驟3:繪製子View   預設為空白實現 單一View中不需要實現,ViewGroup中已經實現該方法        dispatchDraw(canvas);        ........        // 步驟4:繪製滑動條和前景色彩等等        onDrawScrollBars(canvas);       ..........        return;    }    ...    }

無論是ViewGroup還是單一的View,都需要實現這套流程,不同的是,在ViewGroup中,實現了 dispatchDraw()方法,而在單一子View中不需要實現該方法。自訂View一般要重寫onDraw()方法,在其中繪製不同的樣式。

View繪製流程:

 

五、總結

從View的測量、布局和繪製原理來看,要實現自訂View,根據自訂View的種類不同,可能分別要自訂實現不同的方法。但是這些方法不外乎:onMeasure()方法,onLayout()方法,onDraw()方法。

onMeasure()方法:單一View,一般重寫此方法,針對wrap_content情況,規定View預設的大小值,避免於match_parent情況一致。ViewGroup,若不重寫,就會執行和單子View中相同邏輯,不會測量子View。一般會重寫onMeasure()方法,迴圈測量子View。

**onLayout()方法:**單一View,不需要實現該方法。ViewGroup必須實現,該方法是個抽象方法,實現該方法,來對子View進行布局。

**onDraw()方法:**無論單一View,或者ViewGroup都需要實現該方法,因其是個空方法

 

 

六、參考文章

  https://github.com/LRH1993/android_interview/blob/master/android/basis/custom_view.md

 

Android面試收集錄12 View測量、布局及繪製原理

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.