前言
上一篇中我們講到了Android的觸摸事件傳遞機(jī)制梦鉴,除此之外肥橙,關(guān)于Android
View
的繪制流程這一塊也是View
相關(guān)的核心知識(shí)點(diǎn)存筏。我們都知道,PhoneWindow
是Android
系統(tǒng)中最基本的窗口系統(tǒng)搏色,每個(gè)Activity
會(huì)創(chuàng)建一個(gè)继榆。同時(shí)略吨,PhoneWindow
也是Activity
和View
系統(tǒng)交互的接口翠忠。DecorView
本質(zhì)上是一個(gè)FrameLayout
秽之,是Activity
中所有View
的祖先考榨。
一河质、開(kāi)始:DecorView
被加載到Window
中
從Activity
的startActivity
開(kāi)始掀鹅,最終調(diào)用到ActivityThread
的handleLaunchActivity
方法來(lái)創(chuàng)建Activity
乐尊,相關(guān)核心代碼如下:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
....
// 創(chuàng)建Activity扔嵌,會(huì)調(diào)用Activity的onCreate方法
// 從而完成DecorView的創(chuàng)建
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
handleResumeActivity(r.tolen, false, r.isForward, !r.activity..mFinished && !r.startsNotResumed);
}
}
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
unscheduleGcIdler();
mSomeActivitiesChanged = true;
// 調(diào)用Activity的onResume方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
...
if (r.window == null &&& !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
// 得到DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 得到了WindowManager对人,WindowManager是一個(gè)接口
// 并且繼承了接口ViewManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
// WindowManager的實(shí)現(xiàn)類是WindowManagerImpl姻几,
// 所以實(shí)際調(diào)用的是WindowManagerImpl的addView方法
wm.addView(decor, l);
}
}
}
}
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
...
}
在了解View
繪制的整體流程之前,我們必須先了解下ViewRoot
和DecorView
的概念咱台。ViewRoot
對(duì)應(yīng)于ViewRootImpl
類回溺,它是連接WindowManager
和DecorView
的紐帶遗遵,View
的三大流程均是通過(guò)ViewRoot
來(lái)完成的车要。在ActivityThread
中翼岁,當(dāng)Activity
對(duì)象被創(chuàng)建完畢后琅坡,會(huì)將DecorView
添加到Window
中榆俺,同時(shí)會(huì)創(chuàng)建ViewRootImpl
對(duì)象谴仙,并將ViewRootImpl
對(duì)象和DecorView
建立關(guān)聯(lián)晃跺,相關(guān)源碼如下所示:
// WindowManagerGlobal的addView方法
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
ViewRootImpl root;
View pannelParentView = null;
synchronized (mLock) {
...
// 創(chuàng)建ViewRootImpl實(shí)例
root = new ViewRootImpl(view..getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
// 把DecorView加載到Window中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
二、了解繪制的整體流程
繪制會(huì)從根視圖ViewRoot
的performTraversals()
方法開(kāi)始烹玉,從上到下遍歷整個(gè)視圖樹(shù)二打,每個(gè)View控件負(fù)責(zé)繪制自己继效,而ViewGroup
還需要負(fù)責(zé)通知自己的子View
進(jìn)行繪制操作瑞信。performTraversals()
的核心代碼如下凡简。
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
//執(zhí)行測(cè)量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//執(zhí)行布局流程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//執(zhí)行繪制流程
performDraw();
}
performTraversals
的大致工作流程圖如下所示:
注意:
-
preformLayout
和performDraw
的傳遞流程和performMeasure
是類似的帜乞,唯一不同的是挖函,performDraw
的傳遞過(guò)程是在draw
方法中通過(guò)dispatchDraw
來(lái)實(shí)現(xiàn)的怨喘,不過(guò)這并沒(méi)有本質(zhì)區(qū)別必怜。 - 獲取
content
:
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
- 獲取設(shè)置的
View
:
content.getChildAt(0);
三梳庆、理解MeasureSpec
1.MeasureSpec
源碼解析
MeasureSpec
表示的是一個(gè)32位的整形值膏执,它的高2位表示測(cè)量模式SpecMode
更米,低30位表示某種測(cè)量模式下的規(guī)格大小SpecSize
征峦。MeasureSpec
是View
類的一個(gè)靜態(tài)內(nèi)部類栏笆,用來(lái)說(shuō)明應(yīng)該如何測(cè)量這個(gè)View
蛉加。MeasureSpec
的核心代碼如下针饥。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0X3 << MODE_SHIFT;
// 不指定測(cè)量模式, 父視圖沒(méi)有限制子視圖的大小打厘,子視圖可以是想要
// 的任何尺寸户盯,通常用于系統(tǒng)內(nèi)部,應(yīng)用開(kāi)發(fā)中很少用到硫眨。
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// 精確測(cè)量模式礁阁,視圖寬高指定為match_parent或具體數(shù)值時(shí)生效姥闭,
// 表示父視圖已經(jīng)決定了子視圖的精確大小棚品,這種模式下View的測(cè)量
// 值就是SpecSize的值铜跑。
public static final int EXACTLY = 1 << MODE_SHIFT;
// 最大值測(cè)量模式锅纺,當(dāng)視圖的寬高指定為wrap_content時(shí)生效伞广,此時(shí)
// 子視圖的尺寸可以是不超過(guò)父視圖允許的最大尺寸的任何尺寸嚼锄。
public static final int AT_MOST = 2 << MODE_SHIFT;
// 根據(jù)指定的大小和模式創(chuàng)建一個(gè)MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
// 微調(diào)某個(gè)MeasureSpec的大小
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return make MeasureSpec(0, UNSPECIFIED);
}
int size = getSize(measureSpec) + delta;
if (size < 0) {
size = 0;
}
return makeMeasureSpec(size, mode);
}
}
MeasureSpec
通過(guò)將SpecMode
和SpecSize
打包成一個(gè)int
值來(lái)避免過(guò)多的對(duì)象內(nèi)存分配,為了方便操作沧侥,其提供了打包和解包的方法宴杀,打包方法為上述源碼中的makeMeasureSpec
旺罢,解包方法源碼如下:
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
2.DecorView
的MeasureSpec
的創(chuàng)建過(guò)程:
//desiredWindowWidth和desiredWindowHeight是屏幕的尺寸
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
private static int getRootMeaureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATRCH_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;
}
3.子元素的MeasureSpec
的創(chuàng)建過(guò)程
// ViewGroup的measureChildWithMargins方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 子元素的MeasureSpec的創(chuàng)建與父容器的MeasureSpec和子元素本身
// 的LayoutParams有關(guān),此外還和View的margin及padding有關(guān)
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);
child..measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimesion) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// padding是指父容器中已占用的空間大小炉旷,因此子元素可用的
// 大小為父容器的尺寸減去padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (sepcMode) {
// 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) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimesion == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size....
// find out how big it should be
resultSize = 0;
resultMode == MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
普通View
的MeasureSpec
的創(chuàng)建規(guī)則如下:
注意:UNSPECIFIED
模式主要用于系統(tǒng)內(nèi)部多次Measure
的情形抽高,一般不需關(guān)注翘骂。
結(jié)論:對(duì)于DecorView
而言碳竟,它的MeasureSpec
由窗口尺寸和其自身的LayoutParams
共同決定莹桅;對(duì)于普通的View
诈泼,它的MeasureSpec
由父視圖的MeasureSpec
和其自身的LayoutParams
共同決定铐达。
四瓮孙、View
繪制流程之Measure
1.Measure
的基本流程
由前面的分析可知杭抠,頁(yè)面的測(cè)量流程是從performMeasure
方法開(kāi)始的偏灿,相關(guān)的核心代碼流程如下忿墅。
private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
// 具體的測(cè)量操作分發(fā)給ViewGroup
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
// 在ViewGroup中的measureChildren()方法中遍歷測(cè)量ViewGroup中所有的View
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
// 當(dāng)View的可見(jiàn)性處于GONE狀態(tài)時(shí),不對(duì)其進(jìn)行測(cè)量
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
// 測(cè)量某個(gè)指定的View
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 根據(jù)父容器的MeasureSpec和子View的LayoutParams等信息計(jì)算
// 子View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// View的measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// ViewGroup沒(méi)有定義測(cè)量的具體過(guò)程,因?yàn)閂iewGroup是一個(gè)
// 抽象類疟游,其測(cè)量過(guò)程的onMeasure方法需要各個(gè)子類去實(shí)現(xiàn)
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
// 不同的ViewGroup子類有不同的布局特性颁虐,這導(dǎo)致它們的測(cè)量細(xì)節(jié)各不相同另绩,如果需要自定義測(cè)量過(guò)程笋籽,則子類可以重寫(xiě)這個(gè)方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// setMeasureDimension方法用于設(shè)置View的測(cè)量寬高
setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 如果View沒(méi)有重寫(xiě)onMeasure方法,則會(huì)默認(rèn)調(diào)用getDefaultSize來(lái)獲得View的寬高
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 = sepcSize;
break;
}
return result;
}
2.對(duì)getSuggestMinimumWidth
的分析
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinmumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
如果View
沒(méi)有設(shè)置背景,那么返回android:minWidth
這個(gè)屬性所指定的值州叠,這個(gè)值可以為0留量;如果View設(shè)置了背景楼熄,則返回android:minWidth
和背景的最小寬度這兩者中的最大值可岂。
3.自定義View時(shí)手動(dòng)處理wrap_content
時(shí)的情形
直接繼承View
的控件需要重寫(xiě)onMeasure
方法并設(shè)置wrap_content
時(shí)的自身大小缕粹,否則在布局中使用wrap_content
就相當(dāng)于使用match_parent
平斩。解決方式如下:
protected void onMeasure(int widthMeasureSpec,
int height MeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widtuhSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
// 在wrap_content的情況下指定內(nèi)部寬/高(mWidth和mHeight)
int heightSpecSize = MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(widthSpecSize, mHeight);
}
}
4.LinearLayout
的onMeasure
方法實(shí)現(xiàn)解析
protected void onMeasure(int widthMeasureSpec, int hegithMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
// measureVertical核心源碼
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
...
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if need)
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
}
系統(tǒng)會(huì)遍歷子元素并對(duì)每個(gè)子元素執(zhí)行measureChildBeforeLayout
方法,這個(gè)方法內(nèi)部會(huì)調(diào)用子元素的measure
方法晚凿,這樣各個(gè)子元素就開(kāi)始依次進(jìn)入measure
過(guò)程歼秽,并且系統(tǒng)會(huì)通過(guò)mTotalLength
這個(gè)變量來(lái)存儲(chǔ)LinearLayout
在豎直方向的初步高度情组。每測(cè)量一個(gè)子元素燥筷,mTotalLength
就會(huì)增加,增加的部分主要包括了子元素的高度以及子元素在豎直方向上的margin
等院崇。
// LinearLayout測(cè)量自己大小的核心源碼
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK荆责;
...
setMeasuredDimension(resolveSizeAndSize(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
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:
// 高度不能超過(guò)父容器的剩余空間
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
5.在Activity
中獲取某個(gè)View
的寬高
由于View
的measure
過(guò)程和Activity
的生命周期方法不是同步執(zhí)行的亚脆,如果View
還沒(méi)有測(cè)量完畢做院,那么獲得的寬/高就是0濒持。所以在onCreate
键耕、onStart
、onResume
中均無(wú)法正確得到某個(gè)View
的寬高信息柑营。解決方式如下:
-
Activity/View
中onWindowFocusChanged
// 此時(shí)View已經(jīng)初始化完畢
// 當(dāng)Activity的窗口得到焦點(diǎn)和失去焦點(diǎn)時(shí)均會(huì)被調(diào)用一次
// 如果頻繁地進(jìn)行onResume和onPause屈雄,那么onWindowFocusChanged也會(huì)被頻繁地調(diào)用
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasureWidth();
int height = view.getMeasuredHeight();
}
}
view.post(runnable)
// 通過(guò)post可以將一個(gè)runnable投遞到消息隊(duì)列的尾部,// 然后等待Looper調(diào)用次runnable的時(shí)候官套,View也已經(jīng)初
// 始化好了
protected void onStart() {
super.onStart();
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
ViewTreeObserver
// 當(dāng)View樹(shù)的狀態(tài)發(fā)生改變或者View樹(shù)內(nèi)部的View的可見(jiàn)// 性發(fā)生改變時(shí)酒奶,onGlobalLayout方法將被回調(diào)
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
View.measure(int widthMeasureSpec, int heightMeasureSpec)
五携御、View
的繪制流程之Layout
1.Layout
的基本流程
// ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
// View.java
public void layout(int l, int t, int r, int b) {
...
// 通過(guò)setFrame方法來(lái)設(shè)定View的四個(gè)頂點(diǎn)的位置贮匕,即View在父容器中的位置
boolean changed = isLayoutModeOptical(mParent) ?
set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
...
onLayout(changed, l, t, r, b);
...
}
// 空方法,子類如果是ViewGroup類型,則重寫(xiě)這個(gè)方法捷雕,實(shí)現(xiàn)ViewGroup
// 中所有View控件布局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
2.LinearLayout
的onLayout
方法實(shí)現(xiàn)解析
protected void onlayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l,)
}
}
// layoutVertical核心源碼
void layoutVertical(int left, int top, int right, int bottom) {
...
final int count = getVirtualChildCount();
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.getMeasureWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
...
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
// 為子元素確定對(duì)應(yīng)的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
// childTop會(huì)逐漸增大际跪,意味著后面的子元素會(huì)被
// 放置在靠下的位置
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child,i)
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
注意:在View
的默認(rèn)實(shí)現(xiàn)中宪睹,View
的測(cè)量寬/高和最終寬/高是相等的团搞,只不過(guò)測(cè)量寬/高形成于View
的measure
過(guò)程,而最終寬/高形成于View
的layout
過(guò)程摆尝,即兩者的賦值時(shí)機(jī)不同温艇,測(cè)量寬/高的賦值時(shí)機(jī)稍微早一些。在一些特殊的情況下則兩者不相等:
- 重寫(xiě)
View
的layout
方法,使最終寬度總是比測(cè)量寬/高大100px
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r + 100, b + 100);
}
-
View
需要多長(zhǎng)measure
才能確定自己的測(cè)量寬/高,在前幾次測(cè)量的過(guò)程中堕汞,其得出的測(cè)量寬/高有可能和最終寬/高不一致勺爱,但最終來(lái)說(shuō),測(cè)量寬/高還是和最終寬/高相同
六讯检、View
的繪制流程之Draw
1.Draw
的基本流程
private void performDraw() {
...
draw(fullRefrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset,
scalingRequired, dirty)) {
return;
}
...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo,
int xoff, int yoff, boolean scallingRequired, Rect dirty) {
...
mView.draw(canvas);
...
}
// 繪制基本上可以分為六個(gè)步驟
public void draw(Canvas canvas) {
...
// 步驟一:繪制View的背景
drawBackground(canvas);
...
// 步驟二:如果需要的話邻寿,保持canvas的圖層,為fading做準(zhǔn)備
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left, top, right, top + length, null, flags);
...
// 步驟三:繪制View的內(nèi)容
onDraw(canvas);
...
// 步驟四:繪制View的子View
dispatchDraw(canvas);
...
// 步驟五:如果需要的話视哑,繪制View的fading邊緣并恢復(fù)圖層
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// 步驟六:繪制View的裝飾(例如滾動(dòng)條等等)
onDrawForeground(canvas)
}
2.setWillNotDraw
的作用
// 如果一個(gè)View不需要繪制任何內(nèi)容,那么設(shè)置這個(gè)標(biāo)記位為true以后誊涯,
// 系統(tǒng)會(huì)進(jìn)行相應(yīng)的優(yōu)化挡毅。
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
- 默認(rèn)情況下,
View
沒(méi)有啟用這個(gè)優(yōu)化標(biāo)記位暴构,但是ViewGroup
會(huì)默認(rèn)啟用這個(gè)優(yōu)化標(biāo)記位跪呈。 - 當(dāng)我們的自定義控件繼承于
ViewGroup
并且本身不具備繪制功能時(shí),就可以開(kāi)啟這個(gè)標(biāo)記位從而便于系統(tǒng)進(jìn)行后續(xù)的優(yōu)化取逾。 - 當(dāng)明確知道一個(gè)
ViewGroup
需要通過(guò)onDraw
來(lái)繪制內(nèi)容時(shí)耗绿,我們需要顯示地關(guān)閉WILL_NOT_DRAW
這個(gè)標(biāo)記位。