前言
Android的視圖是如何繪制的?深入了解一下UI的繪制原理無論對我們APP的性能優化還是對我們的自定義view都有很大的幫助。下文将和大家一道探究一下Android的viewTree的繪制原理,希望對大家的開發和學習有所幫助。
本篇是圖解Android系列第二篇,更多文章敬請關注後續文章。如果這篇文章對大家學習Android有幫助,還望大家多多轉載。學習小組QQ群: 193765960。
Activity的視圖結構
先看一下activity的視圖結構圖
Activity的視圖結構
每個activity都有一個Window(實際是phonewindow)
Phonewindow含有一個DecorView,這是我們window的topview
DecorView是繼承自Framelayout,換言之其為整個ViewTree的根節點viewGroup
再看一下Phonewindow的類圖
Activity的視圖結構
接下來我們來看一下單個Activity的viewTree的結構,我選擇了兩版sdk來檢視
1)Android4.4系統的activity:
Activity的視圖結構
2)Android6.0系統的activity:
Activity的視圖結構
ViewTree的繪制
id為“content”的ContentFrameLayout是我們的布局檔案加載顯示的區域,更确切地說是我們activity的setcontentView()方法設定的視圖顯示的區域。下面我麼就看看ContentFrameLayout中整個viewTree是如何繪制出來的。
Activity的視圖結構
在《圖解Android:事件傳遞機制》中我們說過Android中的任何一個布局、任何一個控件包括我們自定義的控件其實都是直接或間接繼承自View實作的,是以說這些View應該都具有相同的繪制流程與機制才能顯示到螢幕上(可能每個控件的具體繪制邏輯有差異, 但是主流程都是一樣的)。每一個View的繪制過程都必須經曆三個最主要的過程,也就是measure()、layout()和draw()。
先看一下類圖:
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。