Android自定義View繪制流程詳解
Android 中 Activity
Android 中 Activity 是作為應(yīng)用程序的載體存在,代表著一個(gè)完整的用戶界面,提供了一個(gè)窗口來(lái)繪制各種視圖,當(dāng) Activity 啟動(dòng)時(shí),我們會(huì)通過(guò) setContentView 方法來(lái)設(shè)置一個(gè)內(nèi)容視圖,這個(gè)內(nèi)容視圖就是用戶看到的界面。那么 View 和 activity 是如何關(guān)聯(lián)在一起的呢 ?
Android的UI層級(jí)繪制體系

上圖是View與Activity之間的關(guān)系,先介紹一下上面這張圖
- PhoneWindow:每個(gè)Activity都會(huì)創(chuàng)建一個(gè)Window用來(lái)承載View的顯示,Window是一個(gè)抽象類,PhoneWindow是Window的唯一實(shí)現(xiàn)類,該類中包含一個(gè)DecorView。
- DecorView:最頂層的View,該View繼承自 FrameLayout,它的內(nèi)部包含兩部分,一部分是ActionBar ,另一部分ContentView,
- ContentView:我們 setContentView() 中傳入的布局,就在該View中加載顯示
- ViewRootImpl:視圖層次結(jié)構(gòu)的頂部。一個(gè) Window 對(duì)應(yīng)著一個(gè) ViewRootImpl 和 一個(gè)DecorView,通過(guò)該實(shí)例對(duì)DecorView進(jìn)行控制,最終通過(guò)執(zhí)行ViewRootImpl的performTraversals()開啟整個(gè)View樹的繪制,
View的加載流程
- 當(dāng)調(diào)用 Activity 的setContentView 方法后會(huì)調(diào)用PhoneWindow 類的setContentView方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
- PhoneWindow類的setContentView方法中最終會(huì)生成一個(gè)DecorView對(duì)象
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//在這里生成一個(gè)DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
}
private void installDecor() {
mForceDecorInstall = false;
//mDecor 為DecorView
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
...
}
protected DecorView generateDecor(int featureId) {
...
// 在這里直接 new 了一個(gè)DecorView
return new DecorView(context, featureId, this, getAttributes());
}
- DecorView容器中包含根布局,根布局中包含一個(gè)id為content的FrameLayout布局,Activity加載布局的xml最后通過(guò)LayoutInflater將xml文件中的內(nèi)容解析成View層級(jí)體系,最后填加到id為content的FrameLayout布局中。
protected ViewGroup generateLayout(DecorView decor) {
//做一些窗體樣式的判斷
...
//給窗體進(jìn)行裝飾
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
//加載系統(tǒng)布局 判斷到底是加載那個(gè)布局
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
}
...
mDecor.startChanging();
//將加載到的基礎(chǔ)布局添加到mDecor中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//通過(guò)系統(tǒng)的content的資源ID去進(jìn)行實(shí)例化這個(gè)控件
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
} 到此,Actvity的繪制完成
View的視圖繪制流程剖析
- DecorView被加載到Window中
在ActivityThread的 handleResumeActivity() 方法中通過(guò)WindowManager將DecorView加載到Window中,通過(guò)ActivityThread中一下代碼可以得到應(yīng)征
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
//在此處執(zhí)行Activity的onResume方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
//獲取window對(duì)象
r.window = r.activity.getWindow();
//獲取DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//獲取WindowManager,在這里getWindowManager()實(shí)質(zhì)上獲取的是ViewManager的子類對(duì)象WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
//獲取ViewRootImpl對(duì)象
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
//在這里WindowManager將DecorView添加到PhoneWindow中
wm.addView(decor, l);
}
} 總結(jié):在ActivityThread的handleResumeActivity方法中WindowManager將DecorView添加到PhoneWindow中,addView()方法執(zhí)行時(shí)將視圖添加的動(dòng)作交給了ViewRootImpl處理,最后在ViewRootImpl的performTraversals中開始View樹的繪制
ViewRootImpl的performTraversals()方法完成具體的視圖繪制流程
private void performTraversals() {
if (!mStopped || mReportNextDraw) {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// Ask host how big it wants to be
//View繪制:開始測(cè)量 View的測(cè)量時(shí)遞歸逐層測(cè)量,由父布局與子布局共同確認(rèn)子View的測(cè)量模式,在子布局測(cè)量完畢時(shí)確認(rèn)副布局的寬高,
//在此方法執(zhí)行完畢后才可獲取到View的寬高,否側(cè)獲取的寬高都為0
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
if (didLayout) {
//開始擺放,該方法是ViewGroup中的方法,例如 LinerLayout...
performLayout(lp, mWidth, mHeight);
}
if (!cancelDraw && !newSurface) {
//開始繪制,執(zhí)行View的onDraw()方法
performDraw();
}
} 下面開始對(duì)performMeasure(),performLayout(),performDraw()進(jìn)行解析
- performMeasure()
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
通過(guò)以上這段代碼,我們可以看到兩個(gè)重要的參數(shù) childWidthMeasureSpec,childHeightMeasureSpec,這兩個(gè)Int類型的參數(shù)包含了View的測(cè)量模式和寬高信息,因此在onMeasure()方法中我們可以通過(guò)該參數(shù)獲取到測(cè)量模式,和寬高信息,我們?cè)趏nMeasue中設(shè)置寬高信息也是通過(guò)MeasureSpec設(shè)置,
*/
public static class MeasureSpec {
//int類型占4個(gè)字節(jié),其中高2位表示尺寸測(cè)量模式,低30位表示具體的寬高信息
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
//如下所示是MeasureSpec中的三種模式:UNSPECIFIED、EXACTLY、AT_MOST
//UNSPECIFIED:未指定模式,父容器不限制View的大小,一般用于系統(tǒng)內(nèi)部的測(cè)量
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//AT_MOST:最大模式,對(duì)應(yīng)于在xml文件中指定控件大小為wrap_content屬性,子View的最終大小是父View指定的大小值,并且子View的大小不能大于這個(gè)值
public static final int EXACTLY = 1 << MODE_SHIFT;
//EXACTLY :精確模式,對(duì)應(yīng)于在xml文件中指定控件為match_parent屬性或者是具體的數(shù)值,父容器測(cè)量出View所需的具體大小
public static final int AT_MOST = 2 << MODE_SHIFT;
//獲取測(cè)量模式
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
//獲取寬高信息
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
...
} performMeasure()會(huì)繼續(xù)調(diào)用mView.measure()方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
//根據(jù)原有寬高計(jì)算獲取不同模式下的具體寬高值
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
...
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//在該方法中子控件完成具體的測(cè)量
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
...
} 從上述代碼片段中可以看到執(zhí)行到了onMeasure()方法,如果該控件為View的話,測(cè)量到此結(jié)束,如果是ViewGroup的話,會(huì)繼續(xù)循環(huán)獲取所有子View,調(diào)用子View的measure方法,下面以LinearLayout為例,繼續(xù)看
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
LinearLayout通過(guò)不同的擺放布局執(zhí)行不同的測(cè)量方法,以measureVertical為例,向下看
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
//獲取子View的個(gè)數(shù)
final int count = getVirtualChildCount();
...
//循環(huán)獲取所有子View
for (int i = 0; i < count; ++i) {
//獲取子View
final View child = getVirtualChildAt(i);
//調(diào)用子View的measure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
....
}
至此,View的測(cè)量流程結(jié)束
View的layout流程分析
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
// 在此處調(diào)用mView的layout()擺放開始
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
/*
*@param l view 左邊緣相對(duì)于父布局左邊緣距離
*@param t view 上邊緣相對(duì)于父布局上邊緣位置
*@param r view 右邊緣相對(duì)于父布局左邊緣距離
*@param b view 下邊緣相對(duì)于父布局上邊緣距離
*/
public void layout(int l, int t, int r, int b) {
...
//記錄 view 原始位置
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//調(diào)用 setFrame 方法 設(shè)置新的 mLeft、mTop、mBottom、mRight 值,
//設(shè)置 View 本身四個(gè)頂點(diǎn)位置
//并返回 changed 用于判斷 view 布局是否改變
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//第二步,如果 view 位置改變那么調(diào)用 onLayout 方法設(shè)置子 view 位置
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//開始調(diào)用 onLayout 在此處根據(jù)子View的寬高及相關(guān)規(guī)則進(jìn)行擺放
onLayout(changed, l, t, r, b);
...
}
}
}
} View的Draw流程分析
private void performDraw() {
...
//調(diào)用draw方法
draw(fullRedrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
...
//View的繪制流程調(diào)用的 drawSoftware() 該方法
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
...
//初始化畫布
canvas = mSurface.lockCanvas(dirty);
...
//開始調(diào)用ViewGroup 和 View的draw方法
mView.draw(canvas);
...
}
public void draw(Canvas canvas) {
drawBackground(canvas);
//ViewGroup 默認(rèn)是不會(huì)調(diào)用OnDraw方法的
if (!dirtyOpaque) onDraw(canvas);
//這個(gè)方法主要是ViewGroup循環(huán)調(diào)用 drawChild()進(jìn)行對(duì)子View的繪制
dispatchDraw(canvas);
}
protected void onDraw(Canvas canvas) {
}
View的onDraw方法只是一個(gè)模版,具體實(shí)現(xiàn)方式,交由我們這些開發(fā)者去進(jìn)行實(shí)現(xiàn)
至此,View的繪制流程完畢
- requestLayout重新繪制視圖
子View調(diào)用requestLayout方法,會(huì)標(biāo)記當(dāng)前View及父容器,同時(shí)逐層向上提交,直到ViewRootImpl處理該事件,ViewRootImpl會(huì)調(diào)用三大流程,從measure開始,對(duì)于每一個(gè)含有標(biāo)記位的view及其子View都會(huì)進(jìn)行測(cè)量、布局、繪制。
- invalidate在UI線程中重新繪制視圖
當(dāng)子View調(diào)用了invalidate方法后,會(huì)為該View添加一個(gè)標(biāo)記位,同時(shí)不斷向父容器請(qǐng)求刷新,父容器通過(guò)計(jì)算得出自身需要重繪的區(qū)域,直到傳遞到ViewRootImpl中,最終觸發(fā)performTraversals方法,進(jìn)行開始View樹重繪流程(只繪制需要重繪的視圖)。
- postInvalidate在非UI線程中重新繪制視圖
這個(gè)方法與invalidate方法的作用是一樣的,都是使View樹重繪,但兩者的使用條件不同,postInvalidate是在非UI線程中調(diào)用,invalidate則是在UI線程中調(diào)用。
以上就是Android自定義View繪制流程詳解的詳細(xì)內(nèi)容,更多關(guān)于Android View繪制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android實(shí)現(xiàn)okHttp的get和post請(qǐng)求的簡(jiǎn)單封裝與使用
這篇文章主要介紹了android實(shí)現(xiàn)okHttp的get和post請(qǐng)求的簡(jiǎn)單封裝與使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
Android中使用itemdecoration實(shí)現(xiàn)時(shí)間線效果
這篇文章主要介紹了Android中使用itemdecoration實(shí)現(xiàn)時(shí)間線效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
Android ListView列表實(shí)現(xiàn)倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了Android ListView列表實(shí)現(xiàn)倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09
Android項(xiàng)目基本結(jié)構(gòu)詳解
這篇文章主要為大家詳細(xì)介紹了Android項(xiàng)目基本結(jié)構(gòu),從最基本的內(nèi)容講起,帶你逐步進(jìn)入用C#進(jìn)行Android應(yīng)用開發(fā)的樂園,感興趣的小伙伴們可以參考一下2016-06-06
Android中3種全屏方法及3種去掉標(biāo)題欄的方法
這篇文章主要介紹了Android中3種全屏方法及3種去掉標(biāo)題欄的方法,二個(gè)問題各給出了3種解決方法,并給出實(shí)例代碼,需要的朋友可以參考下2015-06-06
Android實(shí)現(xiàn)界面定時(shí)刷新功能
在移動(dòng)應(yīng)用中,界面定時(shí)刷新是非常常見的需求,本教程將以一個(gè)“實(shí)時(shí)時(shí)鐘”示例為主線,演示多種常用的定時(shí)刷新的實(shí)現(xiàn)方式,并對(duì)比它們的代碼簡(jiǎn)潔度、性能消耗、生命周期管理與取消機(jī)制,幫助您在項(xiàng)目中快速選型并上手,需要的朋友可以參考下2025-04-04
Android實(shí)現(xiàn)引導(dǎo)頁(yè)的圓點(diǎn)指示器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)引導(dǎo)頁(yè)的圓點(diǎn)指示器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06

