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方法。
@Overridepublic 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方法。
@Overrideprotected 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方法源码:
@Overrideprotected 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方法,如下:
@Overridepublic 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状态没有变化的话,是不会执行对应的绘制方法的。
作者:慕涵盛华
链接:https://www.jianshu.com/p/9abf88b7923b
共同學習,寫下你的評論
評論加載中...
作者其他優質文章
