这里咱的重点是View的绘制,但是想让大家有一个整体的认识,我觉得还是从顶层开始说起,要对应的标题初识,咱这会大致讲解,后面Window机制中回去好好说。
其实每一个页面的根布局是一个DecorView,而我们在Activity中调用setContentView去设置布局,其实只不过是Decorview中的一个子View。DecorView这个所有页面的根视图,其实是一个FrameLayout,里面有一个LinearLayout的子View,该LinearLayout是垂直布局,又有两个子View一个是Title,还有一个是Content也就是我们调用Activity的setContentView所设置的View。
而这个DecorView不是依附在Activity上的而是依附在Window上的,也就是说真正的视图是有window来显示的。window中视图的增删对应的操作类是WindowManager,ViewRoot则是负责连接DecorView和WindowManager的枢纽,通过ViewRoot才开始去绘制DecorView,呈树形结构模型,依次ViewGroup的measure–>layout—>draw去绘制,接着再去调用子View的measure—>layout---->draw完成整个视图的绘制流程。
虽然说绘制流程是呈现树状的形式,从树顶依次往下绘制,也就是说先从ViewGroup开始绘制,依次往子View下绘制。由于ViewGroup是继承View,所以绘制流程的开始方法measure,layout,draw方法都在View中,故我们按照方法去讲解。
咱们先来看看测量的入口,measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
...
}
这么说,不管你是View还是ViewGroup,测量的入口都是View#measure方法,因为ViewGroup继承View。但是ViewGroup和View的测量流程一定不一样,为了区分故ViewGroup和View的视图需要重写onMeasure方法,并且该方法还返回了宽高对应的参数,可以去实现对应的测量逻辑。
那么ViewGroup的测量流程和View的测量流程有什么先后顺序的关联呢?关联如下
由于绘制是从顶部开始,而顶部是DecorView一定是一个ViewGroup,所以一定是ViewGroup开始先绘制。
正如上诉流程描述,我们先来讲解一下View的onMeasure(),可以帮助我们做什么。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//下面为自己添加部分
MeasureSpec.getSize(widthMeasureSpec);
MeasureSpec.getMode(widthMeasureSpec);
MeasureSpec.getSize(heightMeasureSpec);
MeasureSpec.getMode(heightMeasureSpec);
}
其实widthMeasureSpec和heightMeasureSpec所表示的确实和宽高有关,但是并不能拿来直接用,它所表达的是父View结合子View自身的宽高再施加规则所返回的值,需要通过MeasureSpec去解析用法如上。
MeasureSpec则代表着一个32位的int值,高两位表示SpecMode,低30位表示SpecSize。SpecMode表示测量模式,SpecSize则表示某种测量模式下的规格大小。
测量模式对应的值有三种如下图
注意:因为SpecMode所表示的模式是有父View的宽高和子View的宽高共同约束的,下面结论仅针对父亲宽高都是match_parent的时候
模式 | 约束意义 | 对应的值 |
---|---|---|
EXACTLY | 父控件决定给子View一个精确的尺寸 | 1073741824 |
AL_MOST | 父控件会给子View一个尽可能大的尺寸 | -2147483648 |
UNSPECIFIED | 父控件不强加任何约束,它可以是它想要的任何大小 | 0 |
可以这么理解
UNSPECIFIED
一般用不到,一般会出现在系统View绘制中。由于一般博客都没怎么讲解到,在此为了弥补这一空缺,咱们要好好的解释一波。
就拿系统的RecycleView为例子,在Item进行measure也就是测量的时候,如果可以滑动且Item宽高是wrap_content的话,那么接下来Item的onMeasure方法就会收到MeasureSpec.UNSPECIFIED。
**为什么此时是不是AL_MOST?**我们来看看RecycleView去生成Child的约束的方法getChildMeasureSpec
public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,
int childDimension, boolean canScroll) {
int size = Math.max(0, parentSize - padding);
int resultSize = 0;
int resultMode = 0;
if (canScroll) {
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
switch (parentMode) {
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
resultSize = size;
resultMode = parentMode;
break;
// MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
// instead using UNSPECIFIED.
case MeasureSpec.UNSPECIFIED:
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
break;
}
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
} else {
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = parentMode;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {
resultMode = MeasureSpec.AT_MOST;
} else {
resultMode = MeasureSpec.UNSPECIFIED;
}
}
}
//noinspection WrongConstant
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
可以看到当可滑动的时候,child为wrap_content的时候,child的约束的UNSPECIFIED;当不能滚动的时候,若父控件的约束是UNSPECIFIED且child是wrap_content则是UNSPECIFIED。
上面还有一段官方的注解,它表达的意思大致就是能滚动的时候,不应该去限制child的大小。
因为本身RecycleView就是可以滚动的,哪怕是child的的宽高超出了屏幕的范围,也还是可以通过滚动去查看显示,若此时约束为AL_MOST,那么child最大最大的宽高只能是父控件的宽高,这样显然是不合理的。
所以这时候需要UNSPECIFIED的约束,因为父控件不强加任何约束,那么子View想要多少就有多少,想放哪里就放哪里,这才形成了超出屏幕的范围,最终才呈现出滑动的效果。
接下来我们来看看子View的宽高对应的SpecSize和SpecMode,首先我们自定义一个View然后日志输出。
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
System.out.println("width mode " + MeasureSpec.getMode(widthMeasureSpec));
System.out.println("width size " + MeasureSpec.getSize(widthMeasureSpec));
System.out.println("height mode " + MeasureSpec.getMode(heightMeasureSpec));
System.out.println("height size " + MeasureSpec.getSize(heightMeasureSpec));
}
}
创建了两个View一个是宽:match_parent,高:50dp;另一个是宽:wrap_content,宽:wrap_content.
<com.sinosun.csdnnote.views.MyView
android:id="@+id/view_1"
android:layout_width="match_parent"
android:layout_height="50dp"
tools:ignore="MissingConstraints"></com.sinosun.csdnnote.views.MyView>
<com.sinosun.csdnnote.views.MyView
android:id="@+id/view_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints"></com.sinosun.csdnnote.views.MyView>
那么最后的日志如下图,可以根据值去查看SpecMode的值,
先找到View#onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
一共有三个方法
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
代码里也很明白,如果没有设置背景,那么就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 = specSize;
break;
}
return result;
}
这一段代码似乎解决了,为什么AT_MOST所显示的尺寸和EXACTLY,也就是上面view_2的输入日志问题所在。在View的源码里,默认把AT_MOST和EXACTLY类型同一处理,所以也就为什么wrap_content获取到的值是match_parent,所以在自定义VIew的时候,需要对约束进行判断,若EXACTLY则返回对应的SpecSize的值,若是AT_MOST则要返回实际的尺寸
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {//判断是不是ViewGroup
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);
}
这一段源码告诉我们
根据measure的流程图,ViewGroup到底是如何调用child#measure方法呢?我们来看看内部提供的三个方法。
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);
}
}
}
代码其实很简单,就是后去所有的Child,调用measureChild方法,传入Child和ViewGroup自身的约束。所以该方法主要是遍历Child。
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);
}
这一段代码也很好理解,通过父控件的约束、child自身的宽高和padding去生成最终的测量约束MeasureSpec,然后在调用Child#measure,从而可以在重写child#onMeasure方法中去回调到最终的约束值。也就是说padding会参与到约束条件中去。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
}
其实measureChildWithMargins和measureChild类似,只不过measureChildWithMargins会把margin参与到约束的计算中去。
似乎约束是有该方法生成的,那么我们不妨去看看约束条件是如何生成的
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);
}
看到switch,就可以那些约束最终会生成什么SpecMode了
父约束 | 子View宽高 | 子约束 |
---|---|---|
EXACTLY | match_parent/准确的尺寸 | EXCTLY |
EXACTLY | wrap_content | AL_MOST |
AL_MOST | 准确的尺寸 | EXCTLY |
AL_MOST | match_parent/wrap_content | AL_MOST |
UNSPECIFIED | 准确的尺寸 | EXCTLY |
UNSPECIFIED | match_parent/wrap_content | UNSPECIFIED |
可能有些人还是觉得很抽象,那么我来看一下ScrollView的onMeasure看看做了什么?
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
if (getChildCount() > 0) {
final View child = getChildAt(0);
final int widthPadding;
final int heightPadding;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (targetSdkVersion >= VERSION_CODES.M) {
widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
} else {
widthPadding = mPaddingLeft + mPaddingRight;
heightPadding = mPaddingTop + mPaddingBottom;
}
//获取ScrollView的测量高度-padding部分
final int desiredHeight = getMeasuredHeight() - heightPadding;
//若ScrollView的可用空间大于Child需要的空间
if (child.getMeasuredHeight() < desiredHeight) {
final int childWidthMeasureSpec = getChildMeasureSpec(
widthMeasureSpec, widthPadding, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
desiredHeight, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
上诉是ScrollView#onMeasure方法,重在思路。由于ScrollView只能拥有一个Child需我们只能看到一个Child调用measure方法。
就拿ScrollView为例,ScrollView的测量先调用到了measure,由于系统把测量权交给了我们,所以ScrollView需要重写onMeasure,然后计算出自身的约束,再去调用Child#measure方法,从而间接的触发Child#onMeasure方法。
layout的分析流程其实和measure一样的,虽然会最先设置ViewGroup的layout(位置),但是layout入口的实现其实是其父类也就是View的layout。整一个ViewGroup与View的关系如下图
正如上图流程,那么我们先来看看View#layout的入口源码
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;
}
//分析1
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//分析2 setOpticalFrame和setFrame
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//分析3
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
...
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
//分析点1
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
//分析点2
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...
return changed;
}
setOpticalFrame该方法内部实则还是调用setFrame方法
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);
}
可以看看ViewGroup#onLayout,该方法还是一个抽象方法,如果继承ViewGroup必定要实现onLayout
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
那就结合LinearLayout#onLayout来看看,毕竟理论要结合实际嘛
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;
//先计算出第一个Child的top
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);
//设置left
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;
//调用child.layout
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) {
child.layout(left, top, left + width, top + height);
}
其实大体思路很清晰,先计算出第一个Child的Top,然后其余的Child在此基础上依次累加。遍历所有的Child,根据padding和margin计算出left,最后在通过setChildFrame方法,去设置Child#layout方法。
draw表示绘制流程,大体流程也是同measure,layout是一个性质,其实有时候源码的注解可以给我们很多阅读源码的方向。
那么对应的中文就是
绘制遍历执行几个绘制步骤,这些步骤必须按适当的顺序执行
那么我们重点看看3 4点
我们可以看到若要实现绘制内容,则需要去重写onDraw方法即可。那么dispatchDraw则表示绘子View,此时还是需要分类讨论,既然是绘制子View,那么View没有Child所以是空实现;如果是ViewGroup则会去重写dispatchDraw方法。
那么我们来看看LinearLayout的dispatchDraw,来看看是如何实现的
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
//遍历所有Child去设置动画
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
//开始动画
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
//设置动画监听
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
...
// We will draw our child's animation, let's reset the flag
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
final long drawingTime = getDrawingTime();
if (usingRenderNodeProperties) canvas.insertReorderBarrier();
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
int transientIndex = transientCount != 0 ? 0 : -1;
// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
//遍历所有Child
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
//调用drawChild,内部实则调用child.draw
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
所以我们可看到LinearLayout先调用draw方法,通过重写dispatchDraw,去遍历所有Child,接着调用Child#draw,从而触发到我们重写的onDraw去绘制内容。
对于View的宽高获取需要特别注意,不能随随便便的getHeight/Width,因为你无法确定此时的View是否已经绘制完成,所以需要额外注意。
最常用的方式使用很简单,通过post可以将一个runnable投递到消息队列的尾部,等待Looper调用runnable的时候,view已经初始化了
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
重写onWindowFocusChanged方法即可,不过该方法会被多次调用,onResume和onPause都会被调用
public final int getWidth() {
return mRight - mLeft;
}
从源码来看,getWidth所返回的值是mRight和mLeft的差值,而mRight,mLeft表示该View的位置,在layout中会被设置,也就是说当layout调用完毕,确定好View的位置,那么getWidth的值就有了
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
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);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
可以看到getMeasuredWidth的值与mMeasuredWidth有关,而mMeasuredWidth是在onMeasure中得到测量后的宽高通过setMessuredDimension方法去设置保存的,所以getMeasuredWidth的调用最好是在setMessuredDimension方法之后。若在setMessuredDimension之前调用getMeasuredWidth则会返回0。
那么 measure layout draw 三个流程大致讲完,下一篇就是View的事件机制,以及冲突的解决方案。