天天看點

android view 源碼分析,圖解Android:View的繪制機制與源碼解析

前言

Android的視圖是如何繪制的?深入了解一下UI的繪制原理無論對我們APP的性能優化還是對我們的自定義view都有很大的幫助。下文将和大家一道探究一下Android的viewTree的繪制原理,希望對大家的開發和學習有所幫助。

本篇是圖解Android系列第二篇,更多文章敬請關注後續文章。如果這篇文章對大家學習Android有幫助,還望大家多多轉載。學習小組QQ群: 193765960。

Activity的視圖結構

先看一下activity的視圖結構圖

android view 源碼分析,圖解Android:View的繪制機制與源碼解析

Activity的視圖結構

每個activity都有一個Window(實際是phonewindow)

Phonewindow含有一個DecorView,這是我們window的topview

DecorView是繼承自Framelayout,換言之其為整個ViewTree的根節點viewGroup

再看一下Phonewindow的類圖

android view 源碼分析,圖解Android:View的繪制機制與源碼解析

Activity的視圖結構

接下來我們來看一下單個Activity的viewTree的結構,我選擇了兩版sdk來檢視

1)Android4.4系統的activity:

android view 源碼分析,圖解Android:View的繪制機制與源碼解析

Activity的視圖結構

2)Android6.0系統的activity:

android view 源碼分析,圖解Android:View的繪制機制與源碼解析

Activity的視圖結構

ViewTree的繪制

id為“content”的ContentFrameLayout是我們的布局檔案加載顯示的區域,更确切地說是我們activity的setcontentView()方法設定的視圖顯示的區域。下面我麼就看看ContentFrameLayout中整個viewTree是如何繪制出來的。

android view 源碼分析,圖解Android:View的繪制機制與源碼解析

Activity的視圖結構

在《圖解Android:事件傳遞機制》中我們說過Android中的任何一個布局、任何一個控件包括我們自定義的控件其實都是直接或間接繼承自View實作的,是以說這些View應該都具有相同的繪制流程與機制才能顯示到螢幕上(可能每個控件的具體繪制邏輯有差異, 但是主流程都是一樣的)。每一個View的繪制過程都必須經曆三個最主要的過程,也就是measure()、layout()和draw()。

先看一下類圖:

android view 源碼分析,圖解Android:View的繪制機制與源碼解析

Activity的視圖結構

那麼,整個Android的UI繪制機制是從哪裡開始的即入口在哪裡呢?答案就是ViewRootImpl類的performTraversals()方法。ViewRootImpl這個類是一個隐藏類,是以如果你是使用Eclipse開發的話可能看不到這個檔案(AndroidStudio可以),沒關系,根據路徑(androidSDK\android-sdk-windows\sources\android-23\android\view\)去找到ViewRootImpl.Java檔案,然後用文本閱讀工具直接打開就好。

看一下官方對ViewRootImpl的介紹:

上面這段注釋啥意思呢?說白了就是ViewRootImpl是一個window中的viewTree的入口,實作了window對viewTree管理的必需邏輯。

ViewRootImpl類performTraversals()代碼,源代碼長的恐怖,這裡給大家過濾一下

private void performTraversals() {

......

//lp.width和lp.height在建立ViewGroup執行個體時值為MATCH_PARENT

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);

int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

......

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

......

performLayout(lp, desiredWindowWidth, desiredWindowHeight);

......

performDraw();

......

}

//執行rootView的測量

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {

Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");

try {

//ViewGroup的measure()方法

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

} finally {

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

}

//執行layout操作

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,

int desiredWindowHeight) {

mLayoutRequested = false;

mScrollMayChange = true;

mInLayout = true;

final View host = mView;

......

try {

//viewRoot先進行layout

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

mInLayout = false;

//需要layout的子view的數量

int numViewsRequestingLayout = mLayoutRequesters.size();

if (numViewsRequestingLayout > 0) {

//需要layout的子view

ArrayList validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,

false);

if (validLayoutRequesters != null) {

//如果view中有調用requestLayout()方法,則說明界面需要重新整理

mHandlingLayoutInLayoutRequest = true;

int numValidRequests = validLayoutRequesters.size();

for (int i = 0; i < numValidRequests; ++i) {

final View view = validLayoutRequesters.get(i);

view.requestLayout();

}

//整個viewTree重新measure

measureHierarchy(host, lp, mView.getContext().getResources(),

desiredWindowWidth, desiredWindowHeight);

mInLayout = true;

//整個viewTree重新layout

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

mHandlingLayoutInLayoutRequest = false;

// 再次檢查是否有view需要重新整理

validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);

if (validLayoutRequesters != null) {

final ArrayList finalRequesters = validLayoutRequesters;

// Post請求,在下一幀的顯示的時候去執行重新整理

getRunQueue().post(new Runnable() {

@Override

public void run() {

int numValidRequests = finalRequesters.size();

for (int i = 0; i < numValidRequests; ++i) {

final View view = finalRequesters.get(i);

view.requestLayout();

}

}

});

}

}

}

} finally {

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

mInLayout = false;

}

private void performDraw() {

......

try {

draw(fullRedrawNeeded);

} finally {

mIsDrawing = false;

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

......

}

private void draw(boolean fullRedrawNeeded) {

......

if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {

//使用硬體渲染,比如GPU

mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);

} else {

//使用軟體渲染

if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {

return;

}

}

......

}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,

boolean scalingRequired, Rect dirty) {

// Draw with software renderer.

final Canvas canvas;

......

try {

canvas.translate(-xoff, -yoff);

if (mTranslator != null) {

mTranslator.translateCanvas(canvas);

}

canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);

attachInfo.mSetIgnoreDirtyState = false;

mView.draw(canvas);

drawAccessibilityFocusedDrawableIfNeeded(canvas);

} finally {

if (!attachInfo.mSetIgnoreDirtyState) {

attachInfo.mIgnoreDirtyState = false;

}

}

......

return true;

}

measure相關

View類的UI繪制相關函數

//final說明該函數不允許被子類override,不需要關注細節

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

......

//widthMeasureSpec,heightMeasureSpec是由parent決定的

onMeasure(widthMeasureSpec, heightMeasureSpec);

......

}

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//View類的預設實作,如果自定義view的話,需要我們自己override

//child的寬高有來自parent的widthMeasureSpec、heightMeasureSpec和子的MeasureSpecMode共同決定

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

}

注意onMeasure(int widthMeasureSpec, int heightMeasureSpec)入參的含義:

widthMeasureSpec和heightMeasureSpec是parent暴露給child的尺寸

widthMeasureSpec和heightMeasureSpec是32位的數值,其中高16位為MeasureSpecMode,低16位為MeasureSpecSize

MeasureSpecMode有三種取值:

MeasureSpec.EXACTLY:child為精準尺寸(layout_with=mach_parent、24dp的情況)

MeasureSpec.AT_MOST:child為最大尺寸(layout_with=wrap_content的情況)

MeasureSpec.UNSPECIFIED:child未指定尺寸

child的尺寸有parent穿過來的widthMeasureSpec、heightMeasureSpec和子的MeasureSpecMode共同決定

layout相關

View的layout相關代碼

//非final類型,子類可以重載

public void layout(int l, int t, int r, int b) {

......

int oldL = mLeft;

int oldT = mTop;

int oldB = mBottom;

int oldR = mRight;

boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {

onLayout(changed, l, t, r, b);

......

}

......

}

//View的onlayout函數預設為空(如果自定義view中需要,可重載)

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

}

ViewGroup的layout相關代碼

@Override

public final void layout(int l, int t, int r, int b) {

if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {

if (mTransition != null) {

mTransition.layoutChange(this);

}

//view的layout方法

super.layout(l, t, r, b);

} else {

mLayoutCalledWhileSuppressed = true;

}

}

@Override

protected abstract void onLayout(boolean changed,

int l, int t, int r, int b);

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

if (mOrientation == VERTICAL) {

layoutVertical();

} else {

layoutHorizontal();

}

}

//以垂直方向的布局為例

void layoutVertical() {

......

final int count = getVirtualChildCount();

......

//周遊child

for (int i = 0; i < count; i++) {

final View child = getVirtualChildAt(i);

if (child == null) {

childTop += measureNullChild(i);

} else if (child.getVisibility() != GONE) {

final int childWidth = child.getMeasuredWidth();

final int childHeight = child.getMeasuredHeight();

......

//遞歸child調用layout

setChildFrame(child, childLeft, childTop + getLocationOffset(child),

childWidth, childHeight);

......

}

}

}

private void setChildFrame(View child, int left, int top, int width, int height) {

child.layout(left, top, left + width, top + height);

}

注意:

View.layout方法可被重載,ViewGroup.layout為final的不可重載,ViewGroup.onLayout為abstract的,子類必須重載實作自己的位置邏輯。

measure操作完成後得到的是對每個View經測量過的measuredWidth和measuredHeight,layout操作 完成之後得到的是對每個View進行位置配置設定後的mLeft、mTop、mRight、mBottom,這些值都是相對于父View來說的。

凡是layout_XXX的布局屬性基本都針對的是包含子View的ViewGroup的,當對一個沒有父容器的View設定相關layout_XXX屬性是沒有任何意義的。

使用View的getWidth()和getHeight()方法來擷取View測量的寬高,必須保證這兩個方法在onLayout流程之後被調用才能傳回有效值。

draw相關

View的draw相關代碼

public void draw(Canvas canvas) {

......

// Step 1, draw the background, if needed

......

if (!dirtyOpaque) {

drawBackground(canvas);

}

// skip step 2 & 5 if possible (common case)

......

// Step 2, save the canvas' layers

......

if (drawTop) {

canvas.saveLayer(left, top, right, top + length, null, flags);

}

......

// Step 3, draw the content

if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children

dispatchDraw(canvas);

// Step 5, draw the fade effect and restore layers

......

if (drawTop) {

matrix.setScale(1, fadeHeight * topFadeStrength);

matrix.postTranslate(left, top);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(left, top, right, top + length, p);

}

......

// Step 6, draw decorations (scrollbars)

onDrawScrollBars(canvas);

......

}

注意:

View的onDraw()方法為空,需要使用者自己實作

關于draw,官方的注釋已經很清楚,我們需要注意的是第四步:遞歸調用完成viewTree的繪制

dispatchdraw()為空,需要在子類去實作

ViewGroup的draw相關代碼

@Override

protected void dispatchDraw(Canvas canvas) {

boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);

final int childrenCount = mChildrenCount;

final View[] children = mChildren;

......

for (int i = 0; i < childrenCount; i++) {

......

if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {

more |= drawChild(canvas, child, drawingTime);

}

}

while (transientIndex >= 0) {

......

if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||

transientChild.getAnimation() != null) {

more |= drawChild(canvas, transientChild, drawingTime);

}

......

}

if (mDisappearingChildren != null) {

......

for (int i = disappearingCount; i >= 0; i--) {

final View child = disappearingChildren.get(i);

more |= drawChild(canvas, child, drawingTime);

}

}

......

}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {

return child.draw(canvas, this, drawingTime);

}

關于invalidate方法

invalidate系列方法請求重繪View樹(也就是draw方法),如果View大小沒有發生變化就不會調用layout過程,并且隻繪制那 些“需要重繪的”View,也就是哪個View(View隻繪制該View,ViewGroup繪制整個ViewGroup)請求invalidate系 列方法,就繪制該View。

有以下幾種觸發invalidate方法的情況:

直接調用invalidate方法:會繪制調用者本身。

觸發setSelection方法:會繪制調用者本身。

觸發setVisibility方法:當View可視狀态在INVISIBLE轉換VISIBLE時會間接調用invalidate方法,繼而繪制該View。當View的可視狀态在 INVISIBLE\VISIBLE 轉換為GONE狀态時會間接調用requestLayout和invalidate方法,同時由于View樹大小發生了變化,是以會請求measure過 程以及draw過程,同樣隻繪制需要“重新繪制”的視圖。

觸發setEnabled方法:不會重新繪制任何View包括該調用者本身。

觸發requestFocus方法:隻繪制“需要重繪”的View。