Android高階進階之路【一】Android中View繪製流程淺析
前言
一個View,從無到有會走三個流程,也就是老生常談的measure,layout,draw三流程;
我們都知道Android檢視是由一層一層構成的層級結構,直白點說,就是父View包含子View而子View又可以包含子View。所以繪製流程是由最外層的View開始,一步一步向內傳遞執行。而整個過程又是遞迴等待的,最外層的View需要等內層所有的View執行完繪製流程才結束,所以便有了”減少佈局層級,可以有效提升App效能”這一經典總結。
正文
什麼時候開始繪製?
而萬物有始才有終,你不惹他,他也不會動手打你。View的繪製流程是什麼時候開始的?誰觸發的?明白這點後,才去考慮這個過程是怎樣執行的。
我們都清楚Activity中onCreate()方法在setContentView()後,View的寬高是獲取不到的。同時我們知道Activity在onResume()後才完全可見,並且初次在onResume()方法中也是拿不到View的尺寸的,這樣可以推算得出:View的繪製流程是在onResume()方法執行結束後才開始的。那Activity的生命週期方法背後是由誰,又何時呼叫的?
答:ActivityManagerService
ActivityManagerService(以下簡稱AMS))是Androids上層系統中最核心的服務之一,主要負責系統中四大元件的啟動、切換、排程及應用程式的管理和排程等工作。具體詳細內容參考以下地址:
相對而言ActivityThread的main方法是應用程式的入口,main()方法裡做一些初始化工作,其中包括和AMS建立起通訊。
public class ActivityThread{ final ApplicationThread mAppThread = new ApplicationThread(); final Looper mLooper = Looper.myLooper(); final H mH = new H(); final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); public static void main(String[] args) { //初始化lopper Looper.prepareMainLooper(); //初始化ActivityThread ActivityThread thread = new ActivityThread(); //ApplicationThread和AMS建立聯絡 thread.attach(false); //取訊息 Looper.loop(); //loop()方法如果執行結束,未能取到訊息,程式丟擲異常退出。 throw new RuntimeException("Main thread loop unexpectedly exited"); } }
ActivityThread會管理和使用者打交道的Activity,應用所有的Activity都存在ActivityThread中的mActivities集合中,而ActivityThread響應AMS的號召,需要藉助ApplicationThread來接受這個詔令,點進去看全都是生命週期方法。接著呼叫attach()方法讓ApplicationThread和AMS建立聯絡。H類就是一個Handler類,用於傳送生命週期改變的訊息,通知響應操作。
private class ApplicationThread extends IApplicationThread.Stub {
//通知相應的程序執行啟動Activity的操作
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
sendMessage(H.LAUNCH_ACTIVITY, r);
}
public final void scheduleResumeActivity(IBinder token, int processState,
boolean isForward, Bundle resumeArgs) {
sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);
}
//.....
}
對於AMS我也不太懂在這兒提一下明白是怎麼回事就行,以後再慢慢研究。當Activity啟動時會先呼叫到scheduleLaunchActivity()方法,由Handler傳送通知訊息後執行handleLaunchActivity()->performLaunchActivity()->callActivityOnCreate()->Activity.onCreate()。
private class H extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
//該方法中會執行Activity的onCreate()方法。
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
//onResume();
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
}
}
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
//.............
//執行onResume()方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//將DecorView新增到Window上
wm.addView(decor, l);
}
}
//說法二:執行makeVisible()來新增View,但也是新增到Window裡和上面一樣的操作。清楚的小夥伴可以告訴我下。
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
}
onResume()時也一樣,當Activity的狀態發生改變,經過層層呼叫執行到handleResumeActivity()方法,在方法中先呼叫Activity.onResume()方法,再執行WindowManager的addView()方法將Activity的根View(DecorView)新增上去,進而開始繪製流程。這就解釋了為什麼初次在onResume()方法中獲取不到View的寬高。對DecorView不太明白的可以參考Activity中setContentView淺析。地址如下所示:
https://blog.csdn.net/sinat_35938012/article/details/81055380
而WindowManager實現類為WindowManagerImpl,WindowManagerImpl中addView()方法又會呼叫WindowManagerGlobal的addView()方法。參考如下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
······
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//即將開始流程繪製
root.setView(view, wparams, panelParentView);
·······
}
addView()方法中先建立ViewRootImpl物件,隨後執行setView()方法將其和DecorView繫結起來,繪製流程也將由ViewRootImpl()來執行。setView()方法中會執行requestLayout()方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
if (mView == null) {
mView = view;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
}
}
requestLayout()方法走下去會非同步執行performTraversals()方法,View的三大流程都是在該方法中執行的。到這兒我們算是明白View的繪製流程是從哪兒開始的,接下來分析這個過程到底是怎麼做的。
private void performTraversals() {
//計算DecorView根View的MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
measure流程
說到measure流程就不得提到一個類,MeausreSpec。使用該類用一個int值就能記錄View測量的寬高和寬高的測量模式,大大節約開銷。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
//int型別佔4個位元組,1個位元組=8bit(位)。
private static final int MODE_MASK = 0x3 << MODE_SHIFT; //11000000000000000000000000000000
public static final int UNSPECIFIED = 0 << MODE_SHIFT; //00000000000000000000000000000000 聽說用於系統內部,想要多大就給多大。平時也沒有用到過,下面不做分析。
public static final int EXACTLY = 1 << MODE_SHIFT; //01000000000000000000000000000000 精確值模式,對應LayoutParams的match_parent或者固定尺寸
public static final int AT_MOST = 2 << MODE_SHIFT; //10000000000000000000000000000000 最大值模式,對應LayoutParams的wrap_content
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode 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);
}
}
用一句話解釋MeasureSepc:
用位運算的方式來”壓縮”記錄View的測量寬高和測量模式,其中高(前)兩位代表測量模式後三十位代表測量後的尺寸。同時提供”解壓”的方法轉為我們需要的實際數值。
MeasureSpec = MeasureMode+MeasureSize
我們以int mMeausreWidth = makeMeasureSepc(720,MeasureSpec.EXACTLY)為例:
getMode亦是如此
//生成DecorView根View的MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
measure流程開始執行之前,會先計算出DecorView的MeasureSpec。此處mWidth和mHeight就為螢幕的寬高,LayoutParmas都為match_parent。
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
計算出DecorView的MeasureSpec後,執行DecorView的measure()方法開始整個View樹的測量。
private void performMeasure()(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
measure()方法是被final修飾了的,派生類都不能重寫,所有View都會執行到View類的measure()方法。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
onMeasure()方法意在二種:相對於ViewGroup來說
-
測量出子View的MeasureSpec後,再執行子View的measure流程
-
給自己mMeasureWidth&Height賦值。
View的onMeasure()方法就只幹第二件事。我們以下xml佈局為例,當我們呼叫setContentView(R.layout.activity_main)後:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".MainActivity">
<TextView
android:layout_height="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"/>
</LinearLayout>
此時此處DecorView有實現onMeausre方法並且會執行父類FrameLayout的onMeausre()方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//core
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
//設定的前景
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//設定的background
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//給自己的mMeasuredWidth和mMeasuredHeight賦值
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState <<MEASURED_HEIGHT_STATE_SHIFT));
}SizeAndState(maxHeight,heightMeasureSpec,childState<<MEASURED_HEIGHT_STATE_SHIFT));}
onMeasure()方法中遍歷所有子View,通過執行measureChildWithMargins()方法,先計算出子View的MeasureSpec再呼叫子View的measure()方法傳遞執行measure流程。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
//開始LinearLayout的measure流程
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}a
父View在幫助計運算元View的MeasureSpec時有著固定的套路:
-
受父View的MeasureSpec影響
-
受子View自身的LayoutParams影響
-
計算父View剩下可用的區域,減去父View的padding和子View的margin距離和父View已經使用(預定)的區域大小。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父View的寬/高測量模式
int specMode = MeasureSpec.getMode(spec);
//父View的寬/高大小
int specSize = MeasureSpec.getSize(spec);
//父View剩下的可用區域
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
//父View_EXACTLY
case MeasureSpec.EXACTLY:
//如果子View寫si了寬/高
if (childDimension >= 0) {
//子View的MeasureSpec=EXACTLY+寫si的寬/高(si說多了不吉利)
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View的MeasureSpec=EXACTLY+父View剩下的區域
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父View_AT_MOST
case MeasureSpec.AT_MOST:
//如果子View寫死了寬高
if (childDimension >= 0) {
//子View的MeasureSpec=EXACTLY+寫si的寬/高
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View的MeasureSpec=AT_MOST+父View剩下的區域
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父View_UNSPECIFIED從來沒有用到,不做分析
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
getChildMeasureSpec()生產子View的MeasureSpec總結如下:
-
子View寫si寬高:測量模式不受父View影響,全都為EXACTLY,寬高為寫si的寬高
-
子View沒有寫si寬高:如果父View都為AT_MOST,子View想都別想還是為AT_MOST,如果父View為EXACTLY且子View的LayoutParams為match_parent,才為EXACTLY。寬高都為父View剩下的區域。這就很好的明白了為什麼我們自定義View時,如果沒對View的寬高進行處理,View即使是wrap_content也會撐滿整個螢幕了。
如果我們寫si的尺寸超過了有效範圍,比如超出了螢幕或者超過了父View的大小,最終的measureWidth/Height和實際寬高還是寫死的尺寸,只不過超出的區域看不見而已。
ViewGroup在所有子View的measure流程都執行結束後,再呼叫setMeasuredDimension()方法給自己的mMeasureWidth/Height賦值。其實View在執行onMeausre()方法之前,已經由父View(DecorView除外)計算出了一個有效的MeasureSpec,比如在執行performMeasure()方法之前就先一步計算出了DecorView的MeasureSpec,接著在measureChildWithMargins()方法中又先計算出LinearLayout的MeasureSpec,再執行LinearLayout的measure()流程。並且View最終的大小都不會超過這個範圍,即使出現以下情況都是如此:
-
在720-1280螢幕下,給View設定了一張1500-1500的圖片
-
子View的大小已經超過了自己
View最終的mMeasureWidth/Height,是由自身的測量模式,前/背景和子View的大小共同決定的。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState <<MEASURED_HEIGHT_STATE_SHIFT));
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
<com.example.yangjie.application.MyLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:context=".MainActivity">
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("TAG","LinearLayoutWidth="+MeasureSpec.getSize(widthMeasureSpec));
Log.i("TAG","LinearLayoutHeight="+MeasureSpec.getSize(heightMeasureSpec));
Log.i("TAG","MeasureWidth="+getMeasuredWidth());
}
當LinearLayout的LayoutParams時match_parent時好說,LinearLayout的MeasureMode為EXACTLY,size就是父View幫其計算出的MeasureSize。如果LinearLayout的LayoutParams為warp_content,在執行resolveSizeAndState()方法時會走到case MeasureSpec.AT_MOST:裡面去。View最終的寬高會從自身的前/背景大小和子View的大小中選則一個最大值。在FrameLayout中會選出最大的子View的measureWidth/Height,因為FrameLayout的子View都是重疊放在左上角的,所以選出最大的那一個就行了。而LinearLayout會累計所有子View的大小。當然如果這個最大值超過了父View為其測量的MeasureSize,最終View的大小還是為父View為其測量的MeasureSize。specSize | MEASURED_STATE_TOO_SMALL;僅僅只是為了標記一個這個View的測量狀態,在getMeasureWidth/Height()時值還是不變的。
ViewGroup的onMeausre()方法明白之後,再看View的就簡單多了,給View的mMeasureWidth和Height賦值就行了。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
如果我們有給View設定background,getSuggestedMinimumWidth()會獲取該大小,但是getDefaultSize()方法還是會選擇父View幫助測量的MeasureSize。
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;
}
layout流程
相對於measure流程而言,layout和draw流程就簡單得多了,通過Layout流程來確定子View在父View中的位置。子View在父View中的位置,需要4個點來確定,同時也可以通過點的距離來計算出View的大小。
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
performLayout方法中會執行DecorView的layout()方法來開始整個View樹的layout流程。而DecorView包括其他的ViewGroup都沒有另外實現layout()方法,都會執行到View的layout()方法。layout()方法中會先執行setFrme()方法確定View自己在父View中的位置,接著再執行onLayout()方法來遍歷所有的子View,計算出子View在自己心中的位置(4個點)後,再執行子View的layout流程。不同的ViewGroup有著不同的方式來安排子View在自己心中的位置。所以View類中的onLayout()是一個空方法,等著View們自己去實現。自定義ViewGroup的時候如果不在onLayout方法中安排子View的位置,將看不見子View。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
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);
}
}
laout流程,相對於ViewGroup而言:
-
確定自己在父View中的位置
-
遍歷所有子View,計算出在自己心中的位置(4個點)後,再執行子View的layout流程
相對於View(單個View)而言只幹第一件事。
draw流程
performDraw()方法中會執行通過層層呼叫會執行到View的draw()方法。
private void performDraw() {
draw(fullRedrawNeeded);
}
private void draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
mView.draw(canvas);
}
public void draw(Canvas canvas) {
//繪製自己的背景
drawBackground(canvas);
//空實現,繪製自己的內容,自定義時重寫該方法
onDraw(canvas)
//繪製子View
dispatchDraw(canvas);
//繪製前景
onDrawForeground(canvas);
}
draw()方法會繪製一些自己的東西。通過dispatchDraw()方法來傳遞執行子View的draw流程。ViewGroup類中已經實現:
protected void dispatchDraw(Canvas canvas) {
more |= drawChild(canvas, child, drawingTime);
}
總結
View的繪製流程到此結束,不足支援多多包涵,指出共同探討。
本文轉自 【郭霖】 如有侵權,請聯絡刪除。