前言
2020年2月22.距離新年已經過去了大半個月了,依舊的出不了門,依舊的躲在家裏一日三餐,依舊的在家辦公,也不知道下週會不會復工,再次彙總手中各種保存整理的筆記
相關內容後續GitHub更新,想衝擊金三銀四的小夥伴可以找找看看,歡迎star
(順手留下GitHub鏈接,需要獲取相關面試等內容的可以自己去找)
https://github.com/xiangjiana/Android-MS
更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。
可以點擊關於我自行查看
一、View繪製的流程框架
View的繪製是從上往下一層層迭代下來的。DecorView
–>ViewGroup
(— >ViewGroup
)–>View
,按照這個流程從上往下,依次measure
(測量),layout
(布 局),draw
(繪製)
二、Measure流程
顧名思義,就是測量每個控件的大小。
調用measure()
方法,進行一些邏輯處理,然後調用onMeasure()
方法,在其中調用 setMeasuredDimension()
設定View的寬高信息,完成View的測量操作。
public final void measure(int widthMeasureSpec, int heightMeasur eSpec) {
}
measure()
方法中,傳入了兩個參數 widthMeasureSpec, heightMeasureSpec
表示 View的寬高的一些信息。
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
?
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) == 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的佈局流程:
四、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的測量、佈局和繪製原理來看,要實現自定義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頁面試大全,爲打算面試或者正在面試的人提供借鑑的思路
知識彙總的PDF相關內容後續GitHub更新,想衝擊金三銀四的小夥伴可以找找看看,歡迎star
(順手留下GitHub鏈接,需要獲取相關面試等內容的可以自己去找)
https://github.com/xiangjiana/Android-MS
更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。
可以點擊關於我自行查看