1. onMeasure过程
1.1 View
首先介绍的是View的Measeure过程.从measure方法说起,他是View内部的一个方法,并且用final修饰.他是View对象Measure过程的起始点:
public final void measure(int widthMeasureSpec, int heightMeasureSpec){
...
// 如果强制执行layout或者需要layout的情况
if (forceLayout || needsLayout) {
...
// 如果缓存里很早不到或者被要求忽略缓存中心layout
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
}
...
}
在必要的条件下会调用onMeasure方法,这个在自定义View的时候是可以重写的.当然View有个默认的实现.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
这里面调用了setMeasuredDimension方法.
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
...
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
最后是调用了方法.将measuredWidth以及measuredHeight赋值给了对应的全局变量.就是说这边确定了布局的长和宽.那下面看看数值怎么确定的,调用的是getDefaultSize方法:
//这边mMinWidth是xml文件里面配置的最新宽度,而mBackground.getMinimumWidth()是背景图的宽度
//也就是说getSuggestedMinimumWidth获取的是背景和配置值中的最大值
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//这边的size也就是上面getSuggestedMinimumWidth得出的最大值,而measureSpec是包含mode以及size信息的数据
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;
}
这里的measureSpec数据包含了size和mode.通过getMode以及getSize方法获取.
首先specMode取决于配置文件中的wrap,match以及固定数值.其中wrap表示的是AT_MOST,也就是他的最大值不超过父类.而match以及固定数值对应的是EXACTLY,就是说这个值是已经确定的.
在默认View中AT_MOST和EXACTLY是一致的.之所以在textview等控件中不一样,因为被重写了,这也是自定义View所要做的动作.
这里是将经过一系列计算得出的测量值赋值给result.
而UNSPECIFIED的情况比较特殊,他直接取决于View的背景或者自定义的长度.
比如说RcycleView就是强制子类使用UNSPECIFIED模式来,因为recycleView的子类长宽并不固定.
如果子类只有一个View,而且无背景且无设置mMinWidth,那么它的长度将是0.
而如果子类是FrameLayout,那么它的长度是正常的,因为FrameLayout重写了UNSPECIFIED模式.
1.2 ViewGroup
下面介绍下ViewGroup的measure流程:
首先它的measure方法也是用的View的默认方法.因为ViewGroup继承自View,而View的measure方法是不可重写的.
那么下面看下onMeausure,由于不同的ViewGroup差异较大,所以ViewGroup并没有写默认的onMeausure方法.
下面以LinearLayout作为例子分析一下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
分为横向布局和纵向布局,这个比较熟悉.跟进去看下
void measureVertical(int widthMeasureSpec, int heightMeasureSpec){
...
//获取竖直方向的View个数
final int count = getVirtualChildCount();
for (int i = 0; i < count; ++i){
//如果是GONE,则直接跳过
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
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.
// 这些View后续会再进行绘制
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
//该方法内部最终会调用measureChildren(),从而 遍历所有子View
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
//获取子View的高度
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));
...
}
}
...
//计算出减去确定宽度后的剩余的长度
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
//上面如果发现有设置weigth,skippedMeasure == true.那么这边就会重绘
if (skippedMeasure
|| ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)){
...
}
...
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//经过一系列计算,setMeasuredDimension方法设置最终绘制出的结果
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
这边主要是会调用measureChildBeforeLayout方法去遍历子类.其他的同样是调用View的方法,比如苏红最后的setMeasuredDimension方法.也就是说ViewGroup与View的差别主要集中在它多了个统筹的作用.这也符合高内聚低耦合的思想.ViewGroup和View分别处理自己的事情.
2. onLayout过程
2.1 View
同样的从layout开始
public void layout(int l, int t, int r, int b){
...
//判断当前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);
....
}
}
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
//setOpticalFrame最终也会调用setFrame.也就是说在onLayout之前就已经确定了,onLayout是一个只是通知而已.
//在View中onLayout是一个空实现
protected boolean setFrame(int left, int top, int right, int bottom){
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
....
}
2.2 ViewGroup
在ViewGroup中onLayout是一个抽象方法,也就是说强迫继承类实现.
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
下面同样用LinearLayout来分析:
@Override
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 count = getVirtualChildCount();
//遍历子类View
for (int i = 0; i < count; i++){
//调用子类的layout方法
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
可见在View或者ViewGroup中并不会改变本身的数值,因为在onLayout中的时候,位置就已经确定了.onLayout的作用在View中是通知,而在ViewGroup中就是给子View安排位置的作用.
3. onDraw过程
3.1 View
从draw开始看起:
public void draw(Canvas canvas){
// Step 1, draw the background, if needed
// 第一步,画背景
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
//第二步:画自身.这个就是自定义View的主要战场了
if (!dirtyOpaque) onDraw(canvas);
//第三步:画子View
dispatchDraw(canvas);
//第四步:画前景
onDrawForeground(canvas);
// we're done...
//基本就这四步骤了...
return;
}
3.2 ViewGroup
ViewGroup最dispatchDraw实现了该方法.因为Draw发生在控件内部,并不像位置一样,涉及控件之间的关系.
@Override
protected void dispatchDraw(Canvas canvas){
final int childrenCount = mChildrenCount;
//遍历子View并绘制
for (int i = 0; i < childrenCount; i++){
...
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {3
//绘制子类
more |= drawChild(canvas, transientChild, drawingTime);
}
}
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
分析完毕!
参考资料
- 【View系列】震惊!!MeasureSpec.UNSPECIFIED是这么用的?!
- 源码解析Android中View的measure量算过程
- 自定义View Measure过程 最易懂的自定义View原理系列(2)
- Android自定义View基础:测量规格(MeasureSpec)到底是什么?
- 深入理解Android中View绘制三大流程及MeasureSpec详解
- Android自定义View基础:测量规格(MeasureSpec)到底是什么?
- 自定义View Layout过程 - 最易懂的自定义View原理系列(3) 自定义ViewDraw过程 - 最易懂的自定义View原理系列(4)
- androidMeasureSpec的三个测量模式