View的工做流程,就是measure、layout和draw。measure用来测量View的宽高,layout用来肯定View的位置,draw则用来绘制View。这里measure较为复杂主要分析一下,measure流程分为View的measure流程和ViewGroup的measure流程,只不过ViewGroup的measure流程除了要完成本身的测量还要遍历去调用子元素的measure()方法。java
先来看看onMeasure()方法(View.java):android
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}复制代码
在这以前还有个measure()方法直接调用的上面的onMeasure()方法,这里measure()呗final修饰因此没法从新因此主要看看onMeasure()里的setMeasuredDimension()方法:canvas
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}复制代码
大概意思是用来设置View的宽高的,接下来在看看getDefaultSize()方法处理了什么:bash
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是View的测量模式,而specSize是View的测量大小,看到这里咱们有必要先说说MeasureSpec:
MeasureSpec类帮助咱们来测量View,它是一个32位的int值,高两位为specMode (测量的模式),低30位为specSize (测量的大小),测量模式分为三种:微信
UNSPECIFIED:未指定模式,View想多大就多大,父容器不作限制,通常用于系统内部的测量。app
AT_MOST:最大模式,对应于wrap_comtent属性,只要尺寸不超过父控件容许的最大尺寸就行。ide
EXACTLY:精确模式,对应于match_parent属性和具体的数值,父容器测量出View所须要的大小,也就是specSize的值。布局
让咱们回头看看getDefaultSize()方法,很显然在AT_MOST和EXACTLY模式下,都返回specSize这个值,也就是View测量后的大小,而在UNSPECIFIED模式返回的是getDefaultSize()方法的第一次个参数的值,这第一个参数从onMeasure()方法来看是getSuggestedMinimumWidth()方法和getSuggestedMinimumHeight()获得的,那咱们来看看getSuggestedMinimumWidth()方法作了什么,咱们只须要弄懂getSuggestedMinimumWidth()方法,由于这两个方法原理是同样的:post
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}复制代码
很明了,若是View没有设置背景则取值为mMinWidth,若是View设置了背景在取值为max(mMinWidth,mBackground.getMinimumWidth()),取值mMinWidth和mBackground.getMinimumWidth()的最大值,mMinWidth是能够设置的,它对应于android:minWidth这个属性设置的值或者View的setMinimumWidth的值,若是不指定的话则默认为0,mBackground.getMinimumWidth(),这个mBackground是Drawable类型的,看一下Drawable类的getMinimumWidth()方法(Drawable.java):优化
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}复制代码
intrinsicWidth获得的是这个Drawable的固有的宽度,若是固有宽度大于0则返回固有宽度,不然返回0。
综上:getSuggestedMinimumWidth()方法就是:若是View没有设置背景则返回mMinWidth ,若是设置了背景就返回mMinWidth 和Drawable最小宽度两个值的最大值。
讲完了View的measure流程,接下来看看ViewGroup的measure流程,对于ViewGroup,它不仅要measure本身自己,还要遍历的调用子元素的measure()方法,ViewGroup中没有定义onMeasure()方,但他定义了measureChildren()方法,在咱们本身实现onMeasure时能够调用它,也能够不调用(通常测量孩子都调用他),至关于一个模板。在线性布局、相对布局等中都有实现,稍后分析。(ViewGroup.java):
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) {
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);
}复制代码
很简单,遍历孩子,调用measureChild,内部再让孩子去measure,因而就到了View的测量。这里getChildMeasureSpec()方法里写了什么呢?点击去看看:
//三个参数分别是
//1.父View的measurespec
//2.父View已经占用的尺寸,也就是孩子不能使用的(这个是父View的padding+孩子的margin)
//3.子view的width(MATCH_PARENT、WARP_CONTENT、具体数值)
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//父容器的可用尺寸(去掉了padding),若是是负的,那就是0
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:
//孩子的尺寸是具体数值(大于等于0就是具体数值)
if (childDimension >= 0) {
//以下
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//孩子是MATCH_PARENT
} 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 not be
// bigger than us.
//孩子是包裹内容,那么孩子的测量模式就是AT_MOST,而且此时size的含义就是孩子最大可能的尺寸,而不是孩子的具体尺寸了
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
//父亲是AT_MOST,说明父亲的尺寸不肯定,可是父亲最大不能超过某个数值,这个数值是已知了
case MeasureSpec.AT_MOST:
//孩子是具体数值,那孩子就像下面那样,精确模式、具体尺寸
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//孩子是MATCH_PARENT,那么孩子不是精确的,可是孩子能够肯定他最大尺寸,那就是父亲的最大尺寸,模式是AT_MOST
} 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 not be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
//这种状况
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}复制代码
很显然这是根据父容器的MeasureSpec的模式再结合子元素的LayoutParams属性来得出子元素的MeasureSpec属性。
ViewGroup并无提供onMeasure()方法,而是让其子类来各自实现测量的方法,究其缘由就是ViewGroup有不一样的布局的须要很难统一,接下来咱们来简单分析一下ViewGroup的子类LinearLayout的measure流程,先来看看它的onMeasure()方法(LinearLayout.java):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}复制代码
两个方法实现大同小异,这里看下垂直measureVertical()方法的部分源码:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
mTotalLength = 0;
...
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;
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
// Optimization: don not bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
int oldHeight = Integer.MIN_VALUE;
if (lp.height == 0 && lp.weight > 0) {
// heightMode is either UNSPECIFIED or AT_MOST, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
oldHeight = 0;
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).
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));
...
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height复制代码
大体意思就是定义了mTotalLength用来存储LinearLayout在垂直方向的高度,而后遍历子元素,根据子元素的MeasureSpec模式分别计算每一个子元素的高度,若是是wrap_content则将每一个子元素的高度和margin垂直高度等值相加并赋值给mTotalLength得出整个LinearLayout的高度。若是布局高度设置为match_parent者具体数值则和View的测量方法同样。
layout方法用来决定View自身的位置,在layout中调用了onLayout方法,这个方法没有具体的实现,须要子类本身实现,主要是为了决定子View的位置
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//设置自身的位置
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
//调用onLayout,具体的实现都不同
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
if (mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>) 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 &= ~FORCE_LAYOUT;
}复制代码
在Linearlayout中,onLayout中主要就是遍历孩子,而后调用setChildFrame方法,这个方法内部就是调用child的layout方法,因此又回到了上面那一步。
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
// 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);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
// we're done... return; } // Step 2, save the canvas' layers
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
}复制代码
看注释可知,View的draw过程主要有如下几步:
画背景
画内容
画孩子
画装饰
draw是经过dispatchDraw将绘画分发给孩子的
有个方法是setWillNotDraw(),能够设置当前view不绘制内容,通常继承自ViewGroup,而且确保自身不须要绘制,就设为true,能够优化。默认为false。
自定义view这块就大概说一下注意事项,具体不展开。若是你想深刻了解这里强烈推荐一下凯哥的自定义View系列 HenCoder:给高级 Android 工程师的进阶手册,若是还没看过你就out了, 良心巨做,如今好像都开始着手准备国际化了推向国外了,凯哥(扔物线)的“关注我就能达到大师级水平,这话我终于敢说了”可不是盖的。
继承自View的自定义View
在onMeasure中处理wrap_parent
在onDraw中处理padding
自定义xml属性,文件名字不必定要交attrs。自定义属性获取完数据以后记得调用recycle。继承自ViewGroup的自定义View
这里最后放一张HenCoder:给高级 Android 工程师的进阶手册的微信公众号的图片,感谢大神的无私奉献~~~