面試一問:關於 View測量、佈局及繪製原理

前言

2020年2月22.距離新年已通過去了大半個月了,依舊的出不了門,依舊的躲在家裏每日三餐,依舊的在家辦公,也不知道下週會不會復工,再次彙總手中各類保存整理的筆記git

相關內容後續GitHub更新,想衝擊金三銀四的小夥伴能夠找找看看,歡迎star
順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)github

1、View繪製的流程框架

面試一問:關於 View測量、佈局及繪製原理
View的繪製是從上往下一層層迭代下來的。DecorView-->ViewGroup(--- >ViewGroup)-->View ,按照這個流程從上往下,依次measure(測量),layout(布 局),draw(繪製)
面試一問:關於 View測量、佈局及繪製原理面試

2、Measure流程

顧名思義,就是測量每一個控件的大小。canvas

調用measure()方法,進行一些邏輯處理,而後調用onMeasure()方法,在其中調用 setMeasuredDimension()設定View的寬高信息,完成View的測量操做。框架

public final void measure(int widthMeasureSpec, int heightMeasur eSpec) { 
  }

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

protected void onMeasure(int widthMeasureSpec, int heightMeasure Spec) { 
     setMeasuredDimension(getDefaultSize(getSuggestedMinimumW idth(), widthMeasureSpec), 
        getDefaultSize(getSuggestedMinimumHeight(), heig htMeasureSpec)); 
   }

由上述流程來看Measure流程很簡單,關鍵點是在於widthMeasureSpec, heightMeasureSpec這兩個參數信息怎麼得到? 佈局

若是有了widthMeasureSpec, heightMeasureSpec,經過必定的處理(能夠重寫,自 定義處理步驟),從中獲取View的寬/高,調用setMeasuredDimension()方法,指定 View的寬高,完成測量工做。優化

MeasureSpec的肯定

先介紹下什麼是MeasureSpec
面試一問:關於 View測量、佈局及繪製原理
MeasureSpec由兩部分組成,一部分是測量模式,另外一部分是測量的尺寸大
小。 其中,Mode模式共分爲三類spa

UNSPECIFIED 不對View進行任何限制,要多大給多大,通常用於系統內部3d

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

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

那麼MeasureSpec又是如何肯定的?

對於DecorView,其肯定是經過屏幕的大小,和自身的佈局參數LayoutParams

這部分很簡單,根據LayoutParams的佈局格式(match_parentwrap_content或 指定大小),將自身大小,和屏幕大小相比,設置一個不超過屏幕大小的寬高,以 及對應模式。 對於其餘View(包括ViewGroup),其肯定是經過父佈局的MeasureSpec和自身的 佈局參數LayoutParams。 這部分比較複雜。如下列圖表表示不一樣的狀況:
面試一問:關於 View測量、佈局及繪製原理
當子View的LayoutParams的佈局格式是wrap_content,能夠看到子View的大小 是父View的剩餘尺寸,和設置成match_parent時,子View的大小沒有區別。爲了 顯示區別,通常在自定義View時,須要重寫onMeasure方法,處理wrap_content 時的狀況,進行特別指定。

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

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

View的測量流程:
面試一問:關於 View測量、佈局及繪製原理

3、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) == PF LAG_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 bot tom) {
     ... 
  // 經過如下賦值語句記錄下了視圖的位置信息,即肯定View的四個頂點 
  // 即肯定了視圖的位置 
       mLeft = left; 
       mTop = top; 
       mRight = right; 
       mBottom = bottom; 

       mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBott om); 
  }

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

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

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

若是當前View是一個ViewGroup,就須要實現onLayout方法,該方法的實現個自 定義ViewGroup時其特性有關,必須本身實現。

由此便完成了一層層的的佈局工做。 View的佈局流程:
面試一問:關於 View測量、佈局及繪製原理

4、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中不須要實現,ViewG roup中已經實現該方法 
      dispatchDraw(canvas); 
      ........ 
      // 步驟4:繪製滑動條和前景色等等 
      onDrawScrollBars(canvas); 
      .......... 
      return; 

    }
     ... 
  }

不管是ViewGroup仍是單一的View,都須要實現這套流程,不一樣的是,在 ViewGroup中,實現了 dispatchDraw()方法,而在單一子View中不須要實現該方 法。自定義View通常要重寫onDraw()方法,在其中繪製不一樣的樣式。

View繪製流程:

面試一問:關於 View測量、佈局及繪製原理

5、總結

從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都須要實現該方法,因其是個空 方法
本身整理的983頁面試大全,爲打算面試或者正在面試的人提供借鑑的思路
面試一問:關於 View測量、佈局及繪製原理
面試一問:關於 View測量、佈局及繪製原理

上圖知識彙總的PDF相關內容後續GitHub更新,想衝擊金三銀四的小夥伴能夠找找看看,歡迎star
順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)

相關文章
相關標籤/搜索