view的绘制流程
首先总结一下view的绘制流程,再详细的分析
- 在#ViewRootImpl的方法中有一个requestLayou()的方法通过层层的调用(requestLayout() -> scheduleTraversals()-> doTraversal() -> performTraversals()),调用到了#ViewRootImpl的performTraversals,这个performTraversals通过调用performMeasure()、performLayout()、performDraw()来完成我们的view的绘制
- 第一:performMeasure():用于指定和测量layout中所有控件的宽高。先调用meature()方法检测跟之前的Spec是否有改变和requestLayout的标志,再决定是否调用onMeasure()真正开始测量--对于viewGroup控件来讲,它会先计算所有子view的宽高再来计算和指定自己的宽高。对于view控件的宽高则由自己和父布局决定。
- 第二:performLayout():用于摆放子布局,for循环所有子view,用child.layout摆放所有子view。
- 第三:performDraw():用于绘制自己还有子view,对于ViewGroup控件首先绘制自己的背景,最多for循环调用所有子view的draw()方法。对于view控件来说,先绘制自己的背景,然后绘制自己的内容。
对requestLayout()方法到performTraversals()的追踪
#在ViewRootImpl类
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true; //标志为ture,在meature方法中检查
scheduleTraversals(); //追踪这个方法
}
}
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mChoreographer.postCallback(//追踪一下这个mTraversalRunnable
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
//mTraversalRunnable的类
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal(); //继续追踪
}
}
void doTraversal() {
if (mTraversalScheduled) {
...
performTraversals(); //发现调用了我们的performTraversals方法
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
private void performTraversals() {
//这个方法开始调用我们的performMeasure()、performLayout、performDraw()方法。
//这个方法有6.7百行代码,因此从performMeasure()、performLayout、performDraw()开始分析。
}
分析前必备知识MeasureSpec
- 模式:
at_most: warp_parent
exactly: match_parent,100 - 如果父布局的模式为exactly,那么当前的view的大小为match_parent,100时模式也为exactly,如果大小是warp_parent则模式为at_most
- 如果父布局的模式是at_most,那么只有当前的view大小是100(固定值)时是exactly,其他两种都是at_most。
- 所以view的模式由父布局和自己大小决定,而view得大小得信息存在LayoutParams,每个控件都可以由自己特定得LayoutParams,比如RelativeLayout、LinearLayout,它们都是继承ViewGroup.LayoutParams。
Measure
performTraversals方法说起:
#ViewRootImpl类
private void performTraversals() {
...
if (!mStopped) {
//在decorView中mWidth,mHeight是屏幕的高度
//第一次调用时,从DecorView拿到MeasureSpec开始向下遍历测试大小。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
...
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//开始调用View的measure方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
从上面可知,performMeasure会调用measure()方法:
#View类
/**
* 调用这个方法来算出一个View应该为多大。参数为父View对其宽高的约束信息。
* 实际的测量工作在onMeasure()方法中进行
*/
// 调用requestLayout,view的measureSpec跟原来的不一样,或者模式不为exactly,或者size大小不一样了,需要调用onMeasure()方法。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
. . .
// 判断是否需要重新布局
//是否调用requestLayout
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
//跟原来宽高的MeasureSpec是否一样,只要这个不一样就要重新测量
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec;
//MeasureSpec模式要为exactly
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
//跟原来的大小是否一样
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
//对上面做三个判断
final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
// 调用了requesLayout或跟原来的MeasureSpec不一样
if (forceLayout || needsLayout) {
. . .
// 从forceLayout 判断是否有缓存
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 没有缓存,调用onMeasure精确测量view,每个控件和自定义view通过重写的onMesaure()方法确定测量大小。
onMeasure(widthMeasureSpec, heightMeasureSpec);
. . .
} else {
// 缓存命中,直接从缓存中取值即可,不必再测量
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
. . .
}. . . }
//更换新的MeasureSpec
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
从上面得出,meature方法调用了onMeasure()方法进行真正的测量。我们看一下继承ViewGroup的LineaLayout的onMeasure怎么写?
@Override
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);
//如果子孩子为null或Gone时不测量
// 测量子孩子
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
// 高度是子View的高度不断的叠加
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
}
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
// 设置宽高,这里resolveSizeAndState()就是根据VeiwGroup的模式和子view叠加的最大值,来决定取哪个?
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
LinearLayout的onMeasure方法不断的测量子view,如果是垂直方向,不断的叠加height高度。我们看看怎么测量子view。
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//MarginLayoutParams才可以拿到margin
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//这个方法很重要,它生成子view的MessureSpec
//传进去父MessureSpec,padding,lp.width 来确定模式
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);
//生成MeasureSpec,调用measure然后调用view.onMeasure来测量大小
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
getChildMeasureSpec()这个方法很重要,它可以确定view的MeasureSpec。具体的生成模式已经在上面写过了,看看大小怎么确定
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父view的模式和大小
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//父view-padding 确定width最大值
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//父view的模式
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) {
resultSize = size; //大小为width最大值
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size; //大小为width最大值
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension; //大小为固定值
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size; //大小为width最大值
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;//大小为width最大值
resultMode = MeasureSpec.AT_MOST;
}
break;
...
//根据大小的模式生成MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
LinearLayout的onMeasure()就分析完了。我们可以看见这是一个递归的过程,先遍历所有的子view的大小,然后根据所有的子view的大小确定自己的大小。从代码我们可以知道,decorView先自己生成MeasureSpec,调用performMeasure()方法->meature()方法,再调用onMeasure,在这里decorView所有的子view都通过measure->onMeasure来测量大小,不会调用performMeasure()方法。
我们来看一下继承view的控件在没有重写onMeasure方法时,源码会怎么代替测量?
#View类
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 直接调用setMeasuredDimension 确定宽高setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//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.AT_MOST:(这种默认宽高最大值,我们重写时就是为了自己确定它的大小)
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
result = size; //最小值
break;
}
return result;
}
最后measure的方法就分析完了。在总结一遍:
- DecorView调用performTraversals的performMeasure(),然后调用measure方法来查看一下是否跟之前MeasureSpec和是否调用了requestLayout,来决定是否调用onMeasure进行真正的测量。在onMeasure中ViewGroup会先遍历所有的子view并测量它的大小,然后递归算出最大值等,再根据自己模式来确定大小。view的onMeasure方法则根据自己的模式是来确定自己大小,并最终调用setMeasuredDimension来确定最终大小。
Layout
从performTraversals开始
#ViewRootImpl
private void performTraversals() {
...
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
}
rivate void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView; //这个view就是DecorView
// 调用layout()方法,一个view知道左上顶点坐标和宽高就可以确定位置
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
#View
public void layout(int l, int t, int r, int b) {
//setFrame方法是将四个顶点保存在变量中,方便以后取用
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//把四个顶点传给onLayout
onLayout(changed, l, t, r, b);
}
接下来应该到了LinearLayout和FrameLayout中的onLayout()方法
#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 paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
//计算父窗口推荐的子View宽度
final int width = right - left;
//计算父窗口推荐的子View右侧位置
int childRight = width - mPaddingRight;
// Space available for child
//child可使用空间大小
int childSpace = width - paddingLeft - mPaddingRight;
//通过ViewGroup的getChildCount方法获取ViewGroup的子View个数
final int count = getVirtualChildCount();
//获取Gravity属性设置
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//依据majorGravity计算childTop的位置值
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) {
//LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//获取子View的LayoutParams
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);
//依据不同的absoluteGravity计算childLeft位置
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设置child的位置
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);
}
接下来看一下FrameLayout的onLayout方法
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//把父容器的位置参数传递进去
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
final int count = getChildCount();
//以下四个值会影响到子View的布局参数
//parentLeft由父容器的padding和Foreground决定
final int parentLeft = getPaddingLeftWithForeground();
//parentRight由父容器的width和padding和Foreground决定
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//获取子View的测量宽高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//当子View设置了水平方向的layout_gravity属性时,根据不同的属性设置不同的childLeft
//childLeft表示子View的 左上角坐标X值
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
/* 水平居中,由于子View要在水平中间的位置显示,因此,要先计算出以下:
* (parentRight - parentLeft -width)/2 此时得出的是父容器减去子View宽度后的
* 剩余空间的一半,那么再加上parentLeft后,就是子View初始左上角横坐标(此时正好位于中间位置),
* 假如子View还受到margin约束,由于leftMargin使子View右偏而rightMargin使子View左偏,所以最后
* 是 +leftMargin -rightMargin .
*/
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
//水平居右,子View左上角横坐标等于 parentRight 减去子View的测量宽度 减去 margin
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
//如果没设置水平方向的layout_gravity,那么它默认是水平居左
//水平居左,子View的左上角横坐标等于 parentLeft 加上子View的magin值
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
//当子View设置了竖直方向的layout_gravity时,根据不同的属性设置同的childTop
//childTop表示子View的 左上角坐标的Y值
//分析方法同上
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//对子元素进行布局,左上角坐标为(childLeft,childTop),右下角坐标为(childLeft+width,childTop+height)
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
文字总结:
- 确定一个view位置只需要知道左上标和宽高就可以确定,所以DecorView调用performLayout并把自己的左上标(0,0)和宽高传递给了layout()方法,layout把传进来的信息进行保存,并调用onLayout()方法--ViewGroup控件在onLayout中遍历所有的子view,并把所有的子view找到左上标,就是确定top和left的位置,然后调用子view的layout确定位置,就此结束,此不像onMeasure还要往上传。
Draw
从performDraw()看起:
private void performTraversals() {
...
performDraw();
...
}
private void performDraw() {
final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
try {
//传一个是否需要全部绘制的参数到darw()方法
boolean canUseAsync = draw(fullRedrawNeeded);
}finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
//此方法在ViewRootImpl类中
private void draw(boolean fullRedrawNeeded) {
...
//获取mDirty,该值表示需要重绘的区域
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return false;
}
//绘制区域设置为全屏
if (fullRedrawNeeded) {
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
//drawSoftware方法中调用View.draw()
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
//锁定canvas区域,由dirty区域决定
canvas = mSurface.lockCanvas(dirty);
// ... ...
mView.draw(canvas);
}
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
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 绘制view的内容
* 4. Draw children 绘制子view的内容
* 5. If necessary, draw the fading edges and restore layers 绘制View的褪色的边缘,类似于阴影效果
* 6. Draw decorations (scrollbars for instance) 绘制View的装饰
*/
// Step 1, draw the background, if needed
int saveCount;
//此方法先判断是否由背景色,然后拿到layout确定的坐标值,然后移动画布canvas到坐标的地方。
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
onDraw(canvas);//空方法,由控件继承实现,画里面的内容。
// Step 4, draw the children
dispatchDraw(canvas); //绘制子view,是个空方法,不过在viewGroup中实现:主要是遍历所有子view然后判断是否有缓存然后在调用darw()方法
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;
}
draw的总结
- 对于ViewGoup来说是先绘制自己的背景,然后调用onDraw方法遍历所有子view,并给子view调用draw()。对于View来说,在draw先绘制背景,然后调用onDraw方法借助传进来的Canvas类来绘制内容。
- View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。
View的requestLayout、invalidate与postInvalidate
先总结,再详细分析
- requestLayout:子view调用requestLayout,会标记当前view和父容器,同时逐层向上提交,直到viewRootImpl处理该事件,ViewRootImpl调用三大流程,从measure开始对含有标记位的view和子view进行测量、布局、绘制
- invalidate: 子view调用invalidate,会标记该view,同时不断地向父容器请求刷新,父容器经过计算得出自己需要重绘的地方,直到调用viewRootImpl中,最终触发performTraversals方法,不过并不会调用measure、layout方法进行测量、布局,只会调用draw进行重绘制。
- postinvalidate:是在子线程中使用,主要是通过Handler切换到主线程调用invalidate。
requestLayout
// View类:
//首先先判断当前View树是否正在布局流程,接着为当前子View设置标记位
//接着调用mParent.requestLayout方法
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
//为当前view设置标记位 PFLAG_FORCE_LAYOUT
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
//向父容器请求布局,一直到viewRootImpl的requestLayout
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
invalidate
public void invalidate() {
invalidate(true);
}
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
//这里判断该子View是否可见或者是否处于动画中
if (skipInvalidate()) {
return;
}
//根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}
//设置PFLAG_DIRTY标记位
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
//把需要重绘的区域传递给父容器
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//调用父容器的方法,向上传递事件
p.invalidateChild(this, damage);
}
...
}
}
接下来调用viewGroup的invalidateChild()
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
......
do {
......
//循环层层上级调运,直到ViewRootImpl会返回null
parent = parent.invalidateChildInParent(location, dirty);
......
} while (parent != null);
}
#ViewRootImpl
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
......
//View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法
scheduleTraversals();
......
return null;
}
postInvalidate
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
// 只有view加载后才能调用
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
//调用ViewRootImpl的dispatchInvalidateDelayed方法
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
final ViewRootHandler mHandler = new ViewRootHandler();
final class ViewRootHandler extends Handler {
@Override
public String getMessageName(Message message) {
....
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
...
}
}
}
网友评论