以下代碼基于 ANDROID SDK 28也就是android9的憔四。
View的繪制是個(gè)老生常談的問題了咬清,作為一名android開發(fā)人員這是基礎(chǔ)必備的知識(shí)了哲思,了解了view的繪制流程也能更加熟練地掌握自定義控件的技能页畦。下面是本人用一點(diǎn)淺薄的知識(shí)分析的坯临,如有不對(duì)兰粉,敬請(qǐng)指出故痊。
view的首次繪制
==view的第一次繪制是在activityThread中的handleResumeActivity中去實(shí)現(xiàn)的==,activityThread是操作activity的一個(gè)線程玖姑,非常重要的一個(gè)類愕秫。這里先簡單地說下吧。
- 首先整個(gè)系統(tǒng)開機(jī)(加載boot_loader)的時(shí)候
- 會(huì)先出現(xiàn)一個(gè)init進(jìn)程
- init進(jìn)程fork一個(gè)zygote進(jìn)程
- zygote進(jìn)程fork出system_server
- system_server啟動(dòng)后焰络,很多系統(tǒng)的進(jìn)程例如AMS,WMS,PMS都是該進(jìn)程創(chuàng)建后啟動(dòng)的
- launcher進(jìn)程啟動(dòng)后戴甩,觸發(fā)了startActivity,通過binder機(jī)制告訴system_server
- system_server接收到要啟動(dòng)activity的消息,system_server中的AMS會(huì)去通過socket告訴zygote進(jìn)程闪彼,fork出app的進(jìn)程甜孤。
- app進(jìn)程執(zhí)行activityThread的main入口协饲,并初始化==ApplicationThread==(繼承了activityThread,同時(shí)實(shí)現(xiàn)了IBinder的接口)用于和AMS交互
- applicationThread通過binder機(jī)制告訴system_server缴川,我要綁定AMS茉稠,system_server收到通知,向app進(jìn)程發(fā)送handleBindApplication請(qǐng)求把夸,并scheduleLaunchActivity請(qǐng)求而线。
- app進(jìn)程收到請(qǐng)求后,通過handler向activityThread發(fā)送對(duì)應(yīng)的message恋日,執(zhí)行對(duì)應(yīng)的消息膀篮。最后完成對(duì)應(yīng)的生命周期方法onCreate/onResume等。
看完了以上的一個(gè)簡單介紹岂膳,是不是了解一點(diǎn)activityThread誓竿,還不了解沒關(guān)系,記住就好了...上來就跑題真的好嗎...那我們繼續(xù)看下activityThread的handleResumeActivity里做了啥吧闷营。
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
if (r.window == null && !a.mFinished && willBeVisible) {
//獲取一個(gè)window對(duì)象
r.window = r.activity.getWindow();
//獲取decorView烤黍,頂級(jí)view
View decor = r.window.getDecorView();
//先設(shè)置為不可見
decor.setVisibility(View.INVISIBLE);
//拿到viewManger
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;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
//獲取viewRootImpl,操作decorView的大佬傻盟!
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//添加了速蕊,它添加了!就是這里娘赴,可以看到规哲,是我們的viewManager做的!添加了decorView
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
}
}
可以看到在handleResumeActivity中通過wm去addview來添加decorview诽表,wm是windowmanagerImpl唉锌。
windowmangerImpl.addView->windowManagerGlobal.addView->ViewRootImpl.setView->ViewRootImpl.requestLayout就觸發(fā)了第一次的view的繪制。
這就是view的第一次繪制竿奏,是不是很簡單...下面我們還是從第一次說起...眾所周知袄简,activity必須在onCreate里setContentview才能看到界面,這是為啥呢泛啸,why ,how?繼續(xù)看
setContentView源碼分析
activity啟動(dòng)之后會(huì)先在oncreate里去setContentView绿语,如下是activity中的源碼:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow得到的是window,window是個(gè)抽象類候址,唯一實(shí)現(xiàn)的是PhoneWindow吕粹。去phoneWindow中查看是如何setContentView的;
public void setContentView(int layoutResID) {
if (this.mContentParent == null) {
//判斷decorview是否為null岗仑,如果為null匹耕,則創(chuàng)建一個(gè);
this.installDecor();
} else if (!this.hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//已經(jīng)存在了荠雕,則移除所有的view稳其, FEATURE_CONTENT_TRANSITIONS:過渡動(dòng)畫的屬性驶赏;
this.mContentParent.removeAllViews();
}
...
}
重點(diǎn)看這行mLayoutInflater.inflate(layoutResID, this.mContentParent);,有木有很熟悉的趕腳欢际,對(duì)母市,是加載xml的常用方法矾兜,多說一句损趋,通過閱讀源碼可以得出結(jié)論:
- 1.root為null,attachRoot不管為什么椅寺,都沒意義浑槽;
- 2.root不為null,attachRoot為true返帕,會(huì)給布局添加一個(gè)父布局桐玻,即root;
- 3.root不為null荆萤,attachRoot為false镊靴,會(huì)把布局最外層layout的屬性進(jìn)行設(shè)置,當(dāng)被添加到父view中链韭,這些屬性會(huì)生效偏竟;
- 4.root不為null,沒有設(shè)置attachRoot屬性敞峭,則默認(rèn)為true踊谋。
layoutinflater是如何加載的?這里簡單概述一下旋讹。
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
//這是一個(gè)同步的方法
synchronized(this.mConstructorArgs) {
Context inflaterContext = this.mContext;
AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)this.mConstructorArgs[0];
this.mConstructorArgs[0] = inflaterContext;
Object result = root;
try {
InflateException ie;
try {
int type;
while((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
//如果是開始和結(jié)束的標(biāo)簽不做處理
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription() + ": No start tag found!");
}
String name = parser.getName();
//先對(duì)merge標(biāo)簽進(jìn)行處理
if ("merge".equals(name)) {
if (root == null || !attachToRoot) {
//root為空或者不附到根部局則報(bào)錯(cuò)殖蚕,所以merge標(biāo)簽只能在有一個(gè)有效的viewgroup而且attachroot為true才能使
throw new InflateException("<merge /> can be used only with a valid ViewGroup root and attachToRoot=true");
}
//可以看到標(biāo)簽為merge的xml,parent為root
this.rInflate(parser, root, inflaterContext, attrs, false);
} else {
//通過tag創(chuàng)建view
View temp = this.createViewFromTag(root, name, inflaterContext, attrs);
LayoutParams params = null;
if (root != null) {
//先生成params
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
this.rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
//添加view
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException var19) {
ie = new InflateException(var19.getMessage(), var19);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception var20) {
ie = new InflateException(parser.getPositionDescription() + ": " + var20.getMessage(), var20);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
} finally {
this.mConstructorArgs[0] = lastContext;
this.mConstructorArgs[1] = null;
Trace.traceEnd(8L);
}
return (View)result;
}
}
來沉迹,繼續(xù)看這一行 View temp = this.createViewFromTag(root, name, inflaterContext, attrs);睦疫,那么是如何通過tag創(chuàng)建view的呢?
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
try {
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//是系統(tǒng)的view
view = onCreateView(parent, name, attrs);
} else {
//是自定義的view鞭呕,直接創(chuàng)建
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
...
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
//可以看到最終都調(diào)用了createView蛤育,區(qū)別就是如果是系統(tǒng)的view則直接
return createView(name, "android.view.", attrs);
}
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
//加載對(duì)應(yīng)的類
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
//反射獲取構(gòu)造函數(shù)
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//做個(gè)緩存,下次直接使用琅拌,提高效率
sConstructorMap.put(name, constructor);
} else {
...
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//使用構(gòu)造函數(shù)創(chuàng)建view
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
//處理viewstub
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
//省去了一大推拋出異常的catch塊代碼
} catch (Exception e) {
} finally {
}
}
刪掉了一部分代碼缨伊,注意看三個(gè)Factory,分別調(diào)用了onCreateView方法进宝,然后創(chuàng)建了view刻坊。ok,比較明了了党晋,現(xiàn)在的問題點(diǎn)就是這三個(gè)factory是啥谭胚,是什么時(shí)候創(chuàng)建的徐块?
這三個(gè)factory在activity.attach里面已經(jīng)被設(shè)置了。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
}
setPrivateFactory是個(gè)隱形方法灾而,外部不能調(diào)用胡控。而另外兩個(gè)外部用戶可以調(diào)用。
且factory2是調(diào)用的fragment的createView方法旁趟;
所以上面的factory和factory2 是系統(tǒng)hook給我們的方法昼激,如果都沒有設(shè)置,則走默認(rèn)的創(chuàng)建view的流程锡搜。
默認(rèn)的view的創(chuàng)建流程橙困,最終還是通過反射,去獲取類的構(gòu)造函數(shù)耕餐,然后去新建一個(gè)view凡傅。
以上就是setContentView加載view的流程,至此我們簡單總結(jié)一下從activity創(chuàng)建到view的創(chuàng)建的流程:
- 1.應(yīng)用啟動(dòng)之后肠缔,執(zhí)行activity的oncreate方法夏跷,在方法里setContentView;
- 2.通過調(diào)用phonewindow的setContentView明未,執(zhí)行l(wèi)ayoutinfalter的inflate方法去加載布局槽华;
- 3.在inflate方法里,用XmlPullParser解析xml亚隅;
- 4.解析過程中硼莽,可以手動(dòng)設(shè)置factory,如果未設(shè)置走默認(rèn)創(chuàng)建view的流程煮纵,也就是通過反射調(diào)用類的構(gòu)造新建view懂鸵。
view的繪制流程
從首次view的繪制可知,viewRootImpl調(diào)用requestLayout觸發(fā)了view的第一次繪制行疏。
那么我們繼續(xù)從這里看匆光。
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//檢查是否在主線程
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
ok,這個(gè)方法里首先檢查線程酿联,然后把變量置位true终息,最后執(zhí)行scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
ok。終于到了performTraversals了.這里代碼特別長贞让,總的來說是這個(gè)流程:
performTraversals -> performMeasure -> performLayout -> performDraw
performMeasure都會(huì)先去判斷是否需要重新測(cè)量周崭?需要的話調(diào)用measure,其他兩個(gè)方法同理喳张。
View的測(cè)量-onMeasure
自上而下進(jìn)行遍歷续镇。measure方法主要接收兩個(gè)參數(shù)widthMeasureSpec和heightMeasureSpec。而這兩個(gè)值是通過父視圖經(jīng)過計(jì)算后傳給子視圖的销部,而父視圖的這兩個(gè)參數(shù)是在viewRoot里面getRoot方法得到的摸航,根式圖總是充滿全局的制跟。下面來看看measure的核心代碼:
//final方法,不能被重寫
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
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
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}
measure()方法是被用來測(cè)量一個(gè)view到底多大的酱虎。它的實(shí)際測(cè)量工作是在onMeasure里完成的雨膨。它的兩個(gè)參數(shù)widthMeasureSpec和heightMeasureSpec是由父view傳遞進(jìn)來的。對(duì)于子view來說读串,大小是由父view和子view共同決定的聊记。
兩個(gè)很重要的參數(shù):
MeasureSpec
MeasureSpec代表了寬高的尺寸要求,是一個(gè)int型的32位參數(shù)爹土。前兩位代表的是mode后面30位代表的是size甥雕。
mode一般分三種:
- UNSPECIFIED 不指定大小胀茵;
- EXACTLY 精準(zhǔn)大小挟阻;
- AT_MOST 最大值模式琼娘;
makeMeasureSpec將mode和size打包成int型的MeasureSpec;
LayoutParams
LayoutParams被view用于告訴父布局想被怎樣包裹附鸽。
- MATCH_PARENT:該view希望和父布局一樣大脱拼。
- WRAP_CONTENT:該view希望包裹住其內(nèi)容。
ok坷备。繼續(xù)往下看onMeasure熄浓。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure的方法很簡單,就是調(diào)用了setMeasuredDimension().這個(gè)方法其實(shí)就是最終將width和height賦值給了全局變量保存了起來省撑,因此一旦調(diào)用了這個(gè)方法意味著此view的測(cè)量結(jié)束赌蔑。注意注釋里提到的一句話:==必須重寫setMeasureDimension這個(gè)方法,不然會(huì)由measure拋出異常竟秫。==
繼續(xù)看getDefaultSize().
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;
}
如果specMode是AT_MOST和EXACTLY,則尺寸就是specSize娃惯。而默認(rèn)的specSize就是父布局傳入進(jìn)來的。
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
最小的建議寬度和高度是由view的bg和設(shè)置的大小決定的肥败。
ViewGroup的測(cè)量
ViewGroup的測(cè)量從MeasureChilderen開始趾浅,實(shí)際內(nèi)部是遞歸調(diào)用了MeasureChild(==屬性為gone的不測(cè)==量),讓我們直接看MeasureChild這個(gè)方法:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
結(jié)合父布局的MeasureSpec和子view的寬高params等馒稍,再調(diào)用getChildMeasureSpec來調(diào)整子view的measureSpec皿哨,最后調(diào)用子view的measure方法進(jìn)行處理。那繼續(xù)看下getChildMeasureSpec是怎么調(diào)整的
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//獲取當(dāng)前parent view的mode和size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//獲取parent size和padding的差值纽谒,也就是parent的剩余大小证膨。
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 剩余的大小都給你~
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 剩余的大小可以給你,你自己看吧佛舱,不要超過我的大小椎例。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
.....
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
(childDimension是寬高挨决,padding是邊界大小)三個(gè)mode其實(shí)差不多订歪,簡單來說就是:如果發(fā)現(xiàn)childDimension是一個(gè)具體數(shù)值>=0脖祈,那么設(shè)為exactly,如果是match_parent刷晋,resultmode設(shè)置為exactly盖高;如果是wrap_content,設(shè)為AT_MOST;
ok眼虱。至此就是viewGroup的測(cè)量喻奥。
使用view的getMeasureWidth/Height可以獲取view的大小,但是必須在onMeasure流程結(jié)束后獲取捏悬。
View的布局 onLayout
因?yàn)閘ayout也是遞歸結(jié)構(gòu)撞蚕,所以讓我們直接看ViewGroup的layout方法:
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
也是個(gè)final方法,子類不可繼承重寫过牙。調(diào)用了父類的layout甥厦,也就是view的layout方法。
public void layout(int l, int t, int r, int b) {
...
onLayout(changed, l, t, r, b);
...
}
調(diào)用了onLayout寇钉。而ViewGroup的onLayout是個(gè)抽象方法刀疙!也就是說,新建一個(gè)ViewGroup的子類扫倡,必須重寫onLayout方法谦秧。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
而view的onLayout是個(gè)空方法...it's fine。讓我們隨便看個(gè)例子撵溃,frameLayout是如何實(shí)現(xiàn)的:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
會(huì)參考measure過程中計(jì)算得到的width和height來安排子view在父view中的顯示位置疚鲤,然后通過各種gravity的switch語句來決定如何放置子view.measure操作完成后得到的是每個(gè)view經(jīng)過測(cè)量的measureWidth和measureHeight,layout操作完成后得到的是對(duì)每個(gè)view位置分配后的left征懈,right等值石咬。
View的draw分析
viewgroup沒有重寫draw的方法,因此這里也直接分析view的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, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
···
}
注釋寫的很清楚了卖哎,首先繪制背景鬼悠,有必要的話保存圖層质帅,然后==繪制內(nèi)容onDraw==阴挣,傳遞繪制事件恒傻,繪制其他的一些小控件之類的待牵。(第二步和第五步可以忽略)ok铅鲤。我們重點(diǎn)看剩余的四步:
- 1.對(duì)view的背景進(jìn)行繪制drawBackground
- 2.繪制view的內(nèi)容沐序,也就是onDraw棺弊,這個(gè)每個(gè)控件都不一樣帆啃,view里面的onDraw是個(gè)空實(shí)現(xiàn)。
- 3.dispatchView虐秋。也就是對(duì)當(dāng)前view的所有子view進(jìn)行繪制榕茧。本身是個(gè)空實(shí)現(xiàn),需要控件自身進(jìn)行重寫客给,比如FrameLayout這樣繼承了ViewGroup的控件會(huì)去重寫這個(gè)方法用押,然后在方法里去做drawChild的操作;
- 4.對(duì)view的滾動(dòng)條進(jìn)行繪制靶剑。
好了蜻拨,到此為止,view的繪制流程到此結(jié)束桩引。