在Android開發中,View
扮演了很重要的角色。在官方的API中,對View的描述是這樣的:佈局
- View occupies a rectangular area on the screen and is responsible for drawing and event handling
意思是View
在屏幕上佔用一塊矩形區域,而且負責繪製和事件處理。View
的繪製和事件處理是兩個重要的主題。本文簡單分析一下View
的繪製流程。優化
View
的繪製流程是從ViewRootImpl
的performTraversals
方法開始,它通過measure
、layout
和draw
三個過程才能最終將一個View
繪製出來。spa
爲了明白View
的繪製原理,首先要知道MeasureSpec
的概念。MeasureSpec
至關因而View
的測量說明,其中封裝了測量模式(SpecMode)和測量規格(SpecSize)。看一下MeasureSpec
的代碼:.net
private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }
MeasureSpec
將SpecMode和SpecSize封裝成一個int
值,經過getMode
方法能夠提取出SpecMode,getSize
方法能夠提取出SpecSize。code
測量模式(SpecMode)有三種類型:orm
父容器已經檢測出View所須要的精確大小,這個時候View的最終大小就是SpecSize所指定的值。它對應於LayoutParams中的match_parent和具體的數值這兩種模式。blog
父容器指定了一個可用大小即SpecSize,View的大小不能大於這個值,具體是什麼值要看不一樣View的具體實現。它對應於LayoutParams中的wrap_content。遞歸
一個View
的MeasureSpec
由父佈局MeasureSpec
和自身的LayoutParams
共同產生。父佈局的MeasureSpec
從何而來?從父佈局的父佈局而來。最頂層的佈局是DecorView
,經常使用的setContent(view)
即是設置DecorView
。DecorView
的MeasureSpec
是經過ViewRootImpl
中的getRootMeasureSpec
方法獲得的。事件
下面主要分析普通View
的MeasureSpec
的產生,看一下ViewGroup
的measureChild
方法:開發
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
從上面的代碼能夠看出調用getChildMeasureSpec
方法獲得子View
的MeasureSpec
,傳進去的參數是父容器的MeasureSpec
和子View
的LayoutParams
,因而可知MeasurecSpec
是由父容器和View
自己共同決定的。getChildMeasureSpec
方法獲取子View
的MeasureSpec
的邏輯以下圖所示:
其中UNSPECIFID
不須要考慮。
注意當子View
的LayoutParams
爲wrap_content
時,最終的SpecMode都是AT_MOST
,SpecSize爲父容器剩餘空間大小。
獲得子View
的MeasureSpec
後,調用子View
的measure
方法,傳入相應的參數,開始下一層的measure
過程。
View
的measure
的過程由其measure
方法來完成,這是一個final
類型的方法,這意味着子類不能重寫此方法,在View
的方法中去調用View
的onMeasure
方法,它的實現以下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
能夠看出,最終是調用getDefaultSize
方法獲得實際的測量值。上一節提到,當子View
的LayoutParams
爲wrap_content
時,最終的SpecMode都是AT_MOST
,SpecSize爲父容器剩餘空間大小。在getDefaultSize
方法中,對於AT_MOST
和EXACTLY
均是直接使用父容器傳進來的值,這可能不是咱們想要的值,因此自定義View
時要重寫onMeasure
方法處理AT_MOST
,不然使用wrap_content
至關於使用match_parent
。
對於ViewGroup
,測量完本身還要調用子View
的measure
方法,各個子元素再遞歸去執行這個過程。
Layout的做用是ViewGroup
用來肯定子元素的位置,當ViewGroup
的位置被肯定之後,它在onLayout
中會遍歷全部的子元素並調用其layout
方法,在layout
方法中onLayout
方法又會被調用。onlayout
方法是抽象方法,因此自定義ViewGroup
時須要實現這個方法肯定子元素的佈局。經常使用的LinearLayout
以及RelativeLayout
方法均重寫了這個方法。
Draw過程就是將View
繪製到屏幕上,有以下幾步:
View
中有一個特殊的方法setWillNotDraw
,源碼以下:
public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
這個方法的意義是,若是一個View
不須要繪製任何內容,設置這個標記位爲true
後,系統會進行相應的優化。默認狀況下,View
沒有啓用這個標記位,可是ViewGroup
默認啓用。
View
的繪製要通過measure
、layout
以及draw
三個步驟,整體來講仍是比較複雜的,本文只是簡要歸納,更詳細的分析見文末參考。
參考