知識體系:
- ViewRoot和DecorView
- MeasureSpec
- View的工作流程
- measure過程
- layout過程
- draw過程
- 自定義view
- 分類
- 須知
- 事例
- 思想
一、ViewRoot和DecorView
1.ViewRoot對應(yīng)ViewRootImpl纬黎,它是WindowManage和DocorView直接的紐帶唱较。Activity創(chuàng)建完了之后惧磺,通過setContentView添加布局迷扇,鍵DecorView添加到Window中夏醉,同時創(chuàng)建ViewRootImpl,將DecorView和ViewRootImpl關(guān)聯(lián)起來。
理解:為什么要將DecorView和ViewRootImpl關(guān)聯(lián)欠母?
可以將DecorView理解為View的一個容器锚贱,這時候還沒有展示出來仔戈,要通過ViewRoot才能將它展示出來;
2.View創(chuàng)建有三個流程:measure惋鸥、layout杂穷、draw,這三個流程都需要ViewRoot來完成
以performxx開頭的方法都是在RootViewImpl中被調(diào)用的,
①performMeasure:
在RootViewImpl中調(diào)用performMeasure卦绣,在performMeasure中會調(diào)用View的measure方法耐量,在View的measure中又會調(diào)用View的onMeasure;
②performLayout:
③performDraw:
在RootViewImpl中調(diào)用performDraw滤港,在performDraw中會調(diào)用RootViewImpl的draw方法廊蜒,在View的measure中又會調(diào)用View的onMeasure;
從上面這個圖中可以看出溅漾,View的繪制流程是從ViewGroup的performTraversals開始的山叮,依次會調(diào)用performMeasure、performLayout添履、performDraw屁倔,這三個方法完成了頂級View的三個繪制流程。在performMeasure中又會調(diào)用measure,在measure中又會調(diào)用onMeasure暮胧,在onMeasure中則會對所有的子View進行measure過程锐借,這樣就完成了依次measure過程问麸,將繪制過程從父容器傳遞到了子View中,接著在子view會依次執(zhí)行父容器的measure過程钞翔;performLayout過程和上面是一樣的严卖;performDraw有一點點不一樣,只是在draw中通過dispatchDraw傳到子view的布轿,不過并沒有本質(zhì)的區(qū)別
measure之后可以得到View測量后的寬和高哮笆,幾乎所有情況下,這個就是最終的寬和高了(有特殊情況):
通過getMeasuredWidth()/getMeasuredHeight()獲得汰扭;
layout可以確定四個頂點的位置稠肘,通過getTop/getRight/getBottom/getLeft可以獲取四個頂點的坐標(biāo);通過getWidth/getHeight可以獲得最終的寬和高萝毛;
draw是將View顯示在屏幕上的
為什么叫setContentView启具,而不叫setView;
因為DecorView作為頂級View珊泳,他會包含一個LinearLayout,一般由兩部分組成:頂部標(biāo)題欄和底部內(nèi)容欄(id為content)拷沸,而通過setContentView添加布局是添加到內(nèi)容欄中的色查,所以叫setContentView,要獲取內(nèi)容了的view: content = findViewById(R.android.id.content)撞芍,要獲取它的子元素:content.getChilAt(0);
二秧了、MeasureSpec
- MeasureSpec可以理解為一種測量規(guī)范,它會影響View的測量序无,而它的創(chuàng)建又會受父容器的影響验毡,獲得View的layoutparams之后,根據(jù)父容器的一些規(guī)則帝嗡,轉(zhuǎn)換為MeasureSpec晶通。
2.MeasureSpec是int型,32位哟玷,高2位代表SpecMode狮辽,低30位表示SpecSize;這里所說的MeasureSpec都是指它的值;
3.SpecMode有三類:
- UNSPECIFIED:表示父容器不限制子View的大小巢寡,通常用在系統(tǒng)中喉脖,表示一種測量狀態(tài)抑月;
- EXACTLY:表示父容器知道子View的具體大小,對應(yīng)于LayoutParams的match_parent或者是一個準(zhǔn)確的數(shù)值题诵;
- AT_MOST:表示父容器會指定一個大小洁仗,子View的大小有它自己決定,只要不超過指定的大小就行了京痢,對應(yīng)wrap_content;
4.MeasureSpec和LayoutParams的關(guān)系:
對于頂級View(DecorView)來說:
MeasureSpec = 窗口尺寸 + View的LayoutParams
對于普通View來說:
MeasureSpec = 父容器的規(guī)則 + View的LayoutParams
5.對4中的源碼分析:
- 頂級View創(chuàng)建MeasureSpec:
在ViewRootImpl中篷店,先看看 performTraversals():
private void performTraversals(){
......
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
......
}
private boolean measureHierarchy(){
....
if (!goodMeasure) {
//desiredWindowWidth是屏幕的尺寸
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
....
}
/**創(chuàng)建MeasureSpec
* @param int windowSize 屏幕尺寸
* @param int rootDimension LayoutParams中的寬和高
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// 精確模式祭椰,大小是窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 最大模式,大小不能超過specsize
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// layoutparams給的固定值
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
- 普通View創(chuàng)建MeasureSpec:
對于普通View方淤,View的measure過程是從ViewGroup中傳遞的蹄殃,先看看**ViewGroup**的measureChildWithMargins:
protected void measureChildWithMargins(){
//獲取子View的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//創(chuàng)建子View的MeasureSpec
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);
}
/**
* 獲取子View的MeasureSpec
* @param spec : 父容器的MeasureSpec
* @param padding : 間距
* @param childDimension :子View的LayoutParams
* @return
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//大小要減去間距
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) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} 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 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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
可以將上面的規(guī)則用表格分析:
從這個表格可以很清晰的知道讳苦,普通View的MeasureSpec是由父容器的MeasureSpec和view的LayoutParams共同決定的吩谦;
①當(dāng)子View的大小確定的時候式廷,無論父容器是什么SpecMode,子View都顯示的是精確模式滑废,大小為設(shè)置的大腥涑谩;
②當(dāng)子View設(shè)置的是matchparent時逛绵,子view以父容器的mode為準(zhǔn)倔韭,大小為父容器的剩余空間大小/不超過父容器的剩余空間;
③當(dāng)子View設(shè)置為wrap_content時,mode以子view為準(zhǔn)寿酌,前面分析過胰苏,wrap_content對應(yīng)的是AT_MOST,所以子View的mode為AT_MOST,大小不超過父容器的剩余空間醇疼;
④后面兩種情況都不考慮UNSPECIFIED硕并,因為這種模式一般用于系統(tǒng)內(nèi)部多次調(diào)用measure的情況倔毙,不用考慮。
三卵蛉、View的工作流程
- measure過程
1.view的measure過程:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
....
onMeasure(widthMeasureSpec, heightMeasureSpec);
....
}
view的measure方法是final類型么库,子類不能重寫诉儒,所以在他的內(nèi)部調(diào)用了onMeasure方法;
/**
* 設(shè)置測量的數(shù)值
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/**
* 獲取默認(rèn)的大小
* @param size : 系統(tǒng)建議的大小
* @param measureSpec : 獲取的MeasureSpec
* @return
*/
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;
}
/**
* 獲取建議的最小寬度
* @return
*/
protected int getSuggestedMinimumWidth() {
//如果沒有背景胁澳,則為mMinWidth米者,值為android:minWidth設(shè)置的值蔓搞,沒有設(shè)置則為0随橘;
//如果設(shè)置了背景,則為android:minWidth和背景最小寬度的最大值
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
在上面代碼中調(diào)用了mBackground.getMinimumWidth():
//返回的是drawable的原始寬度蒲祈,前提是它有原始寬度才行梆掸,比如ShapeDrawable無原始寬度牙言,BitmapDrawable有原始寬度咱枉;
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
總結(jié):在測量view大小的時候徒恋,exactly和at_most這兩種情況下返回的都是測量的大小欢伏,這里說的是測量的大小而不是最終大小硝拧,是因為最終大小是在layout中確定的;其他情況下返回的是系統(tǒng)建議的最小大小匠璧,如果沒有設(shè)置背景夷恍,則返回的是
android:minwidth
的值媳维,沒有設(shè)置的話就是0,;如果設(shè)置了背景指黎,返回的是android:minwidth
和backgroud最小寬度的最大值醋安,而當(dāng)drawable為背景墓毒,并且有原始大小的時候才能得到background的最小值;
另外在getDefaultSize()中柠辞,我們發(fā)現(xiàn)叭首,當(dāng)為exactly和atmost的時候踪栋,返回的大小都是specsize夷都,當(dāng)設(shè)置layout為wrap_content的時候,根據(jù)表4.1中我們可以看到厢破,它的大小為parentSize,也就是說為parent的剩余寬度笆焰,這時候和設(shè)置為match_parent并沒有區(qū)別嚷掠。所以在自定義View直接繼承View的時候荞驴,要重寫onMeasure,并且規(guī)定當(dāng)specmode為at_most的時候,給它一個固定寬度值/高度值霹娄;
2.ViewGtoup的measure過程:
因為ViewGroup是一個抽象類犬耻,它的具體實現(xiàn)了LinearLayout等的布局都不一樣执泰,所以不用在ViewGroup中實現(xiàn)onMeasure术吝。
//測量子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];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
//先獲取子View的LayoutParams
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);
}
getChildMeasureSpec在上面的代碼中已經(jīng)具體分析過了排苍。
下面來看一下它的一個具體實現(xiàn)類LinearLayout:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
先判斷是水平排列還是垂直排列
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
....
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
// The heightMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal height. We'll restore the original height of 0
// after measurement.
lp.height = LayoutParams.WRAP_CONTENT;
}
// 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 needed).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
// Restore the original height and record how much space
// we've allocated to excess-only children so that we can
// match the behavior of EXACTLY measurement.
lp.height = 0;
consumedExcessSpace += childHeight;
}
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
/**
* If applicable, compute the additional offset to the child's baseline
* we'll need later when asked {@link #getBaseline}.
*/
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
// if we are trying to use a child index for our baseline, the above
// book keeping only works if there are no children above it with
// weight. fail fast to aid the developer.
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
....
}
在測量子view高度的時候则果,用mTotalLength記錄每一個view的高度和寬度還有間距后西壮,通過
setMeasuredDimension
測量LinearLayout的大小,如果它的布局采用的是match_parent做修,則大小為測量的大小,如果為wrap_content蔗坯,則大小為子view的高度和燎含,但是仍然不能超過parent的剩余高度屏箍。
3.在Activity中獲取view的寬和高:
①通過onWindowFocusChanged
:(會調(diào)用多次)
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
int width = mTitleText.getMeasuredWidth();
int height = mTitleText.getMeasuredHeight();
Log.e(TAG,"onWindowFocusChanged:"+width+","+height);
}
}
②通過view.post
:
mTitleText.post(new Runnable() {
@Override
public void run() {
int width = mTitleText.getMeasuredWidth();
int height = mTitleText.getMeasuredHeight();
Log.e(TAG,"onStart-->post:"+width+","+height);
}
});
③通過ViewTreeObserver(會調(diào)用多次)
:
ViewTreeObserver tree = mTitleText.getViewTreeObserver();
tree.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int width = mTitleText.getMeasuredWidth();
int height = mTitleText.getMeasuredHeight();
Log.e(TAG,"onStart-->ViewTreeObserver:"+width+","+height);
}
});
④通過measure
:
這種方式比較復(fù)雜赴魁,而且要區(qū)分不同的情況:
當(dāng)為match_parent的時候,沒法獲取榄棵,因為這時候要知道父容器的剩余大小郎嫁,無法獲取
//具體數(shù)值
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
mTitleText.measure(widthMeasureSpec,heightMeasureSpec);
//wrap_content
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
mTitleText.measure(widthMeasureSpec,heightMeasureSpec);
- layout過程:
layout過程是ViewGroup用來確定子View的位置泽铛,當(dāng)ViewGroup的位置確定了之后,通過for循環(huán)遍歷子元素杠茬,調(diào)用子元素的layout()弛随,在layout()中又會調(diào)用onLayout()確定子元素的位置
在View中
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;
//通過setFrame設(shè)定四個頂點的位置-->這樣view的位置就確定了
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);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
因為onLayout的實現(xiàn)和具體的布局有關(guān)栓票,所以在它的子類中實現(xiàn)就行了:
//它的實現(xiàn)和具體的布局有關(guān)
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
在ViewGroup中:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
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();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//確定子view的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
//調(diào)用子view的layout
child.layout(left, top, left + width, top + height);
}
測量寬高和最終寬高有什么區(qū)別:
實質(zhì)上一般情況下,他們的值是相等的坠狡,只是在measure過程中獲得是測量寬高遂跟,在layout過程中獲得是最終的寬和高,時間上會先獲得測量的寬高凯亮,而在layout過程中触幼,可能會出現(xiàn)這樣的情況,導(dǎo)致他們的值不相等:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right+100, bottom+100);
}
這時候堂鲤,最終寬高會始終比測量寬高大100
- draw過程
是將view繪制在屏幕上媒峡,步驟:
①畫背景:mBackGround.draw()-->canvas
②畫自己:onDraw
③畫子元素:dispatchDraw()
④畫裝飾:onDrawScrollBar()
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 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;
}
}
view還有一個特殊的方法:
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
它的意思是半哟,當(dāng)不需要繪制任何內(nèi)容签餐,設(shè)置為true之后系統(tǒng)會進行相應(yīng)的優(yōu)化,這個方法在view中是默認(rèn)關(guān)閉的戒良,在viewgroup中是默認(rèn)開啟的冠摄;它在實際開發(fā)中的意義是:當(dāng)自定義view繼承自ViewGroup并且本身不具備繪制功能是,開啟它河泳,系統(tǒng)將會進行相應(yīng)的優(yōu)化拆挥;當(dāng)ViewGroup調(diào)用onDraw繪制內(nèi)容時,要**顯示的關(guān)閉WILL_NOT_DRAW **
四惰瓜、自定義view
1.分類:
繼承View食拜,重新onDraw:
可以實現(xiàn)不規(guī)則的效果负甸,但是得重寫OnDraw,需要自己支持padding和wrap_content呻待,有必要時要自定義屬性
繼承ViewGroup蚕捉,重寫onLayout
繼承特定的View(如TextView)
- 繼承特定的ViewGroup(如LinearLayout)