View绘制流程(一)
View的布局
当ViewRootImpl的performTraversals
中performMeasure
执行完成以后会接着执行performLayout
,ViewRootImpl调用performLayout
执行Window对应的View的布局。
- ViewRootImpl的performLayout。
- DecorView(FrameLayout)的layout方法。
- DecorView(FrameLayout)的onLayout方法。
- DecorView(FrameLayout)的layoutChildren方法。
- DecorView(FrameLayout)的所有子View的Layout。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
/*******部分代码省略**********/
//View的布局
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
/*******部分代码省略**********/
final View host = mView;
/*******部分代码省略**********/
try {
//调用View的Layout方法进行布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
//在ViewRootImpl进行布局的期间,Window内的View自己进行requestLayout
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
//请求对该View布局,最终回调到ViewRootImpl的requestLayout进行重新测量、布局、绘制
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
/*******部分代码省略**********/
//请求对该View布局,最终回调到ViewRootImpl的requestLayout进行重新测量、布局、绘制
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
}
layout
方法接收四个参数,这四个参数分别代表相对Parent的左、上、右、下坐标。而且还可以看见左上都为0,右下分别为上面刚刚测量的width和height。
public void layout(int l, int t, int r, int b) {
......
//实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量
//判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新layout
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//需要重新layout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//回调onLayout
onLayout(changed, l, t, r, b);
......
}
......
}
类似measure过程,layout
调用了onLayout
方法,这里需要注意的是layout
方法可以被子类重写,下面看一下View的onLayout
方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
竟然是一个空的方法。,那么就看一下ViewGroup的layout
方法。
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
//调用View的layout方法
super.layout(l, t, r, b);
} else {
mLayoutCalledWhileSuppressed = true;
}
}
本质还是调用View的
layout
方法,这里需要注意的是ViewGroup的layout方法是不能被子类重写的。
接下来看下ViewGroup的onLayout
方法。
@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
ViewGroup的
onLayout()
方法竟然是一个抽象方法,这就是说所有ViewGroup的子类都必须重写这个方法。所以在自定义ViewGroup控件中,onLayout
配合onMeasure
方法一起使用可以实现自定义View的复杂布局。自定义View首先调用onMeasure
进行测量,然后调用onLayout
方法动态获取子View和子View的测量大小,然后进行layout布局。重载onLayout的目的就是安排其children在父View的具体位置,重载onLayout
通常做法就是写一个for循环调用每一个子视图的layout(l, t, r, b)
函数,传入不同的参数l, t, r, b
来确定每个子视图在父视图中的显示位置。
既然View的onLayout
方法为空方法,ViewGroup的onLayout
方法为抽象方法,下面以ViewGroup的子类LinearLayout为例。
public class LinearLayout extends ViewGroup {
@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);
}
}
}
LinearLayout的
layout
过程是分Vertical和Horizontal的,这个就是xml布局的orientation
属性设置的。
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);
}
}
}
实质都是调用
setFrame
方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量。
从上面分析的ViewGroup子类LinearLayout的onLayout
实现代码可以看出,一般情况下layout
过程会参考measure
过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子View在父View中显示的位置,但这不是必须的,measure
过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子View的个数、位置和大小都是固定的,这时候我们可以忽略整个measure
过程,只在layout
方法中传入的4个参数来安排每个子View的具体位置。
到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()
这两对方法之间的区别(上面分析measure
过程已经说过getMeasuredWidth()、getMeasuredHeight()
必须在onMeasure
之后使用才有效)。可以看出来getWidth()与getHeight()
方法必须在layout(int l, int t, int r, int b)
执行之后才有效。那我们看下View源码中这些方法的实现吧,如下:
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
public final int getLeft() {
return mLeft;
}
public final int getRight() {
return mRight;
}
public final int getTop() {
return mTop;
}
public final int getBottom() {
return mBottom;
}
mMeasuredWidth
是一个8位的十六进制数,高两位代表Mode 跟MEASURED_SIZE_MASK
按位&后获得测量后的宽度。
View布局总结
View.layout
方法可被重载,ViewGroup.layout
为final的不可重载,ViewGroup.onLayout
为abstract的,子类必须重载实现自己的位置逻辑。View.onLayout
方法是一个空方法。
measure
操作完成后得到的是对每个View经测量过的measuredWidth和measuredHeight,layout
操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom
,这些值都是相对于父View来说的。
onLayout
中最终循环调用子View的setFrame
方法来设置mLeft、mTop、mRight和mBottom的值。
getMeasuredWidth()、getMeasuredHeight()
必须在onMeasure
之后使用才有效;getWidth()与getHeight()
方法必须在layout(int l, int t, int r, int b)
执行之后才有效。
View的绘制
ViewRootImpl调用performDraw
执行Window对应的View的布局。
- ViewRootImpl的
performDraw
;- ViewRootImpl的
draw
;- ViewRootImpl的
drawSoftware
;- DecorView(FrameLayout)的
draw
方法;- DecorView(FrameLayout)的
dispatchDraw
方法;- DecorView(FrameLayout)的
drawChild
方法;- DecorView(FrameLayout)的所有子View的
draw
方法;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
/*******部分代码省略**********/
//View的绘制
private void performDraw() {
/*******部分代码省略**********/
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
/*******部分代码省略**********/
}
//进行绘制
private void draw(boolean fullRedrawNeeded) {
/*******部分代码省略**********/
//View上添加的Observer进行绘制事件的分发
mAttachInfo.mTreeObserver.dispatchOnDraw();
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
/*******部分代码省略**********/
//调用Window对应的ViewRootImpl的invalidate方法
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
/*******部分代码省略**********/
//绘制Window
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
/**
* @return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
/*******部分代码省略**********/
try {
/*******部分代码省略**********/
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
//View绘制
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
/*******部分代码省略**********/
}
return true;
}
}
由于ViewGroup没有重写View的draw
方法,所以直接看View.draw
方法:
public void draw(Canvas canvas) {
......
// 1. 绘制背景
......
if (!dirtyOpaque) {
drawBackground(canvas);
}
......
//2.绘制View的内容
if (!dirtyOpaque) onDraw(canvas);
//3.对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制。
dispatchDraw(canvas);
......
//4.对View的滚动条进行绘制。
onDrawScrollBars(canvas);
......
}
1.对View的背景进行绘制。
private void drawBackground(Canvas canvas) {
//获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable
final Drawable background = mBackground;
......
//根据layout过程确定的View位置来设置背景的绘制区域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
......
//调用Drawable的draw()方法来完成背景的绘制工作
background.draw(canvas);
......
}
可以看出View背景是一个Drawable,绘制背景最终调用的是
Drawable.draw
2.对View的内容进行绘制。
protected void onDraw(Canvas canvas) {
}
View.onDraw
方法为空方法,ViewGroup也没有重写该方法。因为每个View的内容部分是各不相同的,所以需要由子类去实现具体逻辑。
3.对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制。
View.dispatchDraw()
方法是一个空方法,如果View包含子类需要重写他,所以我们看下ViewGroup.dispatchDraw
方法源码:
@Override
protected void dispatchDraw(Canvas canvas) {
......
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
......
for (int i = disappearingCount; i >= 0; i--) {
......
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
该方法内部会遍历每个子View,然后调用drawChild()
方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
4.对View的滚动条进行绘制。
protected final void onDrawScrollBars(Canvas canvas) {
final ScrollabilityCache cache = mScrollCache;
if (cache != null) {
int state = cache.state;
if (state == ScrollabilityCache.OFF) {
return;
}
boolean invalidate = false;
.........
final boolean drawHorizontalScrollBar = isHorizontalScrollBarEnabled();
final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled()
&& !isVerticalScrollBarHidden();
// Fork out the scroll bar drawing for round wearable devices.
if (mRoundScrollbarRenderer != null) {
if (drawVerticalScrollBar) {
final Rect bounds = cache.mScrollBarBounds;
getVerticalScrollBarBounds(bounds, null);
mRoundScrollbarRenderer.drawRoundScrollbars(
canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds);
if (invalidate) {
invalidate();
}
}
} else if (drawVerticalScrollBar || drawHorizontalScrollBar) {
final ScrollBarDrawable scrollBar = cache.scrollBar;
if (drawHorizontalScrollBar) {
scrollBar.setParameters(computeHorizontalScrollRange(),
computeHorizontalScrollOffset(),
computeHorizontalScrollExtent(), false);
final Rect bounds = cache.mScrollBarBounds;
getHorizontalScrollBarBounds(bounds, null);
onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
bounds.right, bounds.bottom);
if (invalidate) {
invalidate(bounds);
}
}
if (drawVerticalScrollBar) {
scrollBar.setParameters(computeVerticalScrollRange(),
computeVerticalScrollOffset(),
computeVerticalScrollExtent(), true);
final Rect bounds = cache.mScrollBarBounds;
getVerticalScrollBarBounds(bounds, null);
onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
bounds.right, bounds.bottom);
if (invalidate) {
invalidate(bounds);
}
}
}
}
}
可以看见其实任何一个View都是有(水平垂直)滚动条的,只是一般情况下没让它显示而已。
View的invalidate和postInvalidate方法源码分析
View中的invalidate
方法有很多的重载,最终都会调用invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate)
方法(ViewGroup没有重写这些方法)
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
......
// 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);
//传递调运Parent ViewGroup的invalidateChild方法
p.invalidateChild(this, damage);
}
......
}
View.invalidateInternal
方法实质是将要刷新区域直接传递给了父ViewGroup的invalidateChild
方法,在该方法中,调用父View的invalidateChild
,这是一个从当前向上级父View回溯的过程,每一层的父View都将自己的显示区域与传入的刷新Rect做交集 。所以我们看下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的invalidateChildInParent
方法结束,所以我们看下ViewRootImpl的invalidateChildInParent
方法,如下:
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
......
//View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法
scheduleTraversals();
......
return null;
}
最终调用了
scheduleTraversals
方法,该方法会调用ViewRootImpl.performTraversals
方法,重新绘制。
invalidate
该方法只能在UI Thread中执行,其他线程中需要使用postInvalidate
方法
postInvalidate
方法最终会调用ViewRootImpl类的dispatchInvalidateDelayed
方法,源码如下:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
ViewRootImpl类的Handler发送了一条MSG_INVALIDATE
消息,继续追踪这条消息的处理可以发现:
public void handleMessage(Message msg) {
......
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
......
}
......
}
最终在UI线程中调用了View的
invalidate
方法。
直接调用invalidate
方法.请求重新draw,但只会绘制调用者本身。因为其他的View状态没有变化的话,是不会执行对应的绘制方法的。