當一個應用啓動的時候,會啓動一個主activity,android系統會根據activity的佈局來對它進行繪製。每個view負責繪製自己,而viewgroup還需要負責通知自己的子view進行繪製操作。視圖繪製的過程可以分爲三個步驟,分別是 Measure Layout 和 Draw
private void performTraversals() {
......
//最外層的根視圖的widthMeasureSpec和heightMeasureSpec由來
//lp.width和lp.height在創建ViewGroup實例時等於MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
Measure
Measure操作用來計算View的實際大小,對於viewGroup來說,由viewGroup在它的measureChild方法中傳遞給子view,viewGroup通過遍歷自身所有的子view,並逐個調用子view的measure方法完成測量工作。當測量某個指定的view的時候,根據父容器的MeasureSpec和子view的LayoutParams等信息來計算子view的MeasureSpec
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
....
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 遍歷自己的子View,只要不是GONE的都會參與測量,
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
...
....
}
}
//測量某個指定的view
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
// 子View的LayoutParams,你在xml的layout_width和layout_height,
// layout_xxx的值最後都會封裝到這個個LayoutParams。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//根據父容器的MeasureSpec和子view的LayoutParams等信息來計算子view的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
//通過父View的MeasureSpec和子View的自己LayoutParams的計算,算出子View的MeasureSpec,然後父容器傳遞給子容器的
// 然後讓子View用這個MeasureSpec(一個測量要求,比如不能超過多大)去測量自己,如果子View是ViewGroup 那還會遞歸往下測量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
對於每個View的measure方法,最終的測量是通過回調onMeasure方法實現的。這個通常由view的特定子類自己實現,卡發着也可以通過重寫這個方法實現自定義view。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
onMeasure(widthMeasureSpec,heightMeasureSpec);
.....
}
//如果需要自定義測量過程,則子類可以重寫這個方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//如果view沒有重寫onMeasure方法,則默認會調用getDefaultSize來獲得view的寬高
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//@param size參數一般表示設置了android:minHeight屬性或者該View背景圖片的大小值
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: //表示該View的大小父視圖未定,設置爲默認值
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
主要要三種測量模式,
UNSPECIFIED :不指定測量模式,俯視圖沒有限制子視圖餓大小,子視圖可以使想要的任何尺寸,通常用於系統內部,應用開發很少用到
EXACTLY 精確測量模式,當該視圖的layout_width 或者layout_height 指定爲具體數值或者match_parent時生效,表示父視圖已經決定了子視圖的精確大小,這種模式下View的測量值就是SpecSize的值
AT_MOST 最大值模式,當該視圖的layout_width或者layout_height指定爲wrap_content時生效,此時,子視圖的尺寸可以使不超過父視圖允許的最大尺寸的任何尺寸。
Layout
Layout 過程用來確定View在父容器中的佈局位置,它是由父容器獲取子View的位置參數後,調用子View的layout方法並將位置參數傳入實現的,
Draw
Draw用來將控件繪製出來
public void draw(Canvas canvas) {
...
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, 如果需要的話,繪製view背景
...
background.draw(canvas);
...
// skip step 2 & 5 if possible (common case) 一般情況會跳過第二步和第五步
...
// Step 2, save the canvas layers 如果需要的話,保存canvas圖層,爲fading做準備
...
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
...
// Step 3, draw the content 繪製view的內容
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children 繪製view的子view
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers 如果需要的話,繪製View的fading邊緣來恢復圖層
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, top, right, top + length, p);
}
...
// Step 6, draw decorations (scrollbars) 繪製View的裝飾例如滾動條()
onDrawScrollBars(canvas);
}