知识体系:
- ViewRoot和DecorView
- MeasureSpec
- View的工作流程
- measure过程
- layout过程
- draw过程
- 自定义view
- 分类
- 须知
- 事例
- 思想
一、ViewRoot和DecorView
1.ViewRoot对应ViewRootImpl,它是WindowManage和DocorView直接的纽带。Activity创建完了之后,通过setContentView添加布局,键DecorView添加到Window中,同时创建ViewRootImpl,将DecorView和ViewRootImpl关联起来。
理解:为什么要将DecorView和ViewRootImpl关联?
可以将DecorView理解为View的一个容器,这时候还没有展示出来,要通过ViewRoot才能将它展示出来;
2.View创建有三个流程:measure、layout、draw,这三个流程都需要ViewRoot来完成
以performxx开头的方法都是在RootViewImpl中被调用的,
①performMeasure:
在RootViewImpl中调用performMeasure,在performMeasure中会调用View的measure方法,在View的measure中又会调用View的onMeasure;
②performLayout:
③performDraw:
在RootViewImpl中调用performDraw,在performDraw中会调用RootViewImpl的draw方法,在View的measure中又会调用View的onMeasure;
从上面这个图中可以看出,View的绘制流程是从ViewGroup的performTraversals开始的,依次会调用performMeasure、performLayout、performDraw,这三个方法完成了顶级View的三个绘制流程。在performMeasure中又会调用measure,在measure中又会调用onMeasure,在onMeasure中则会对所有的子View进行measure过程,这样就完成了依次measure过程,将绘制过程从父容器传递到了子View中,接着在子view会依次执行父容器的measure过程;performLayout过程和上面是一样的;performDraw有一点点不一样,只是在draw中通过dispatchDraw传到子view的,不过并没有本质的区别
measure之后可以得到View测量后的宽和高,几乎所有情况下,这个就是最终的宽和高了(有特殊情况):
通过getMeasuredWidth()/getMeasuredHeight()获得;
layout可以确定四个顶点的位置,通过getTop/getRight/getBottom/getLeft可以获取四个顶点的坐标;通过getWidth/getHeight可以获得最终的宽和高;
draw是将View显示在屏幕上的
为什么叫setContentView,而不叫setView;
因为DecorView作为顶级View,他会包含一个LinearLayout,一般由两部分组成:顶部标题栏和底部内容栏(id为content),而通过setContentView添加布局是添加到内容栏中的,所以叫setContentView,要获取内容了的view: content = findViewById(R.android.id.content),要获取它的子元素:content.getChilAt(0);
二、MeasureSpec
- MeasureSpec可以理解为一种测量规范,它会影响View的测量,而它的创建又会受父容器的影响,获得View的layoutparams之后,根据父容器的一些规则,转换为MeasureSpec。
2.MeasureSpec是int型,32位,高2位代表SpecMode,低30位表示SpecSize;这里所说的MeasureSpec都是指它的值;
3.SpecMode有三类:
- UNSPECIFIED:表示父容器不限制子View的大小,通常用在系统中,表示一种测量状态;
- EXACTLY:表示父容器知道子View的具体大小,对应于LayoutParams的match_parent或者是一个准确的数值;
- AT_MOST:表示父容器会指定一个大小,子View的大小有它自己决定,只要不超过指定的大小就行了,对应wrap_content;
4.MeasureSpec和LayoutParams的关系:
对于顶级View(DecorView)来说:
MeasureSpec = 窗口尺寸 + View的LayoutParams
对于普通View来说:
MeasureSpec = 父容器的规则 + View的LayoutParams
5.对4中的源码分析:
- 顶级View创建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;
}
}
....
}
/**创建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创建MeasureSpec:
对于普通View,View的measure过程是从ViewGroup中传递的,先看看**ViewGroup**的measureChildWithMargins:
protected void measureChildWithMargins(){
//获取子View的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//创建子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);
}
可以将上面的规则用表格分析:
从这个表格可以很清晰的知道,普通View的MeasureSpec是由父容器的MeasureSpec和view的LayoutParams共同决定的;
①当子View的大小确定的时候,无论父容器是什么SpecMode,子View都显示的是精确模式,大小为设置的大小;
②当子View设置的是matchparent时,子view以父容器的mode为准,大小为父容器的剩余空间大小/不超过父容器的剩余空间;
③当子View设置为wrap_content时,mode以子view为准,前面分析过,wrap_content对应的是AT_MOST,所以子View的mode为AT_MOST,大小不超过父容器的剩余空间;
④后面两种情况都不考虑UNSPECIFIED,因为这种模式一般用于系统内部多次调用measure的情况,不用考虑。
三、View的工作流程
- measure过程
1.view的measure过程:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
....
onMeasure(widthMeasureSpec, heightMeasureSpec);
....
}
view的measure方法是final类型,子类不能重写,所以在他的内部调用了onMeasure方法;
/**
* 设置测量的数值
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/**
* 获取默认的大小
* @param size : 系统建议的大小
* @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设置的值,没有设置则为0;
//如果设置了背景,则为android:minWidth和背景最小宽度的最大值
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
在上面代码中调用了mBackground.getMinimumWidth():
//返回的是drawable的原始宽度,前提是它有原始宽度才行,比如ShapeDrawable无原始宽度,BitmapDrawable有原始宽度;
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
总结:在测量view大小的时候,exactly和at_most这两种情况下返回的都是测量的大小,这里说的是测量的大小而不是最终大小,是因为最终大小是在layout中确定的;其他情况下返回的是系统建议的最小大小,如果没有设置背景,则返回的是
android:minwidth
的值,没有设置的话就是0,;如果设置了背景,返回的是android:minwidth
和backgroud最小宽度的最大值,而当drawable为背景,并且有原始大小的时候才能得到background的最小值;
另外在getDefaultSize()中,我们发现,当为exactly和atmost的时候,返回的大小都是specsize,当设置layout为wrap_content的时候,根据表4.1中我们可以看到,它的大小为parentSize,也就是说为parent的剩余宽度,这时候和设置为match_parent并没有区别。所以在自定义View直接继承View的时候,要重写onMeasure,并且规定当specmode为at_most的时候,给它一个固定宽度值/高度值;
2.ViewGtoup的measure过程:
因为ViewGroup是一个抽象类,它的具体实现了LinearLayout等的布局都不一样,所以不用在ViewGroup中实现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在上面的代码中已经具体分析过了。
下面来看一下它的一个具体实现类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
:(会调用多次)
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(会调用多次)
:
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
:
这种方式比较复杂,而且要区分不同的情况:
当为match_parent的时候,没法获取,因为这时候要知道父容器的剩余大小,无法获取
//具体数值
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的位置,当ViewGroup的位置确定了之后,通过for循环遍历子元素,调用子元素的layout(),在layout()中又会调用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设定四个顶点的位置-->这样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 listenersCopy =
(ArrayList)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的实现和具体的布局有关,所以在它的子类中实现就行了:
//它的实现和具体的布局有关
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) {
//调用子view的layout
child.layout(left, top, left + width, top + height);
}
测量宽高和最终宽高有什么区别:
实质上一般情况下,他们的值是相等的,只是在measure过程中获得是测量宽高,在layout过程中获得是最终的宽和高,时间上会先获得测量的宽高,而在layout过程中,可能会出现这样的情况,导致他们的值不相等:
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);
}
它的意思是,当不需要绘制任何内容,设置为true之后系统会进行相应的优化,这个方法在view中是默认关闭的,在viewgroup中是默认开启的;它在实际开发中的意义是:当自定义view继承自ViewGroup并且本身不具备绘制功能是,开启它,系统将会进行相应的优化;当ViewGroup调用onDraw绘制内容时,要**显示的关闭WILL_NOT_DRAW **
四、自定义view
1.分类:
继承View,重新onDraw:
可以实现不规则的效果,但是得重写OnDraw,需要自己支持padding和wrap_content,有必要时要自定义属性
继承ViewGroup,重写onLayout
继承特定的View(如TextView)
- 继承特定的ViewGroup(如LinearLayout)