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
共同學習,寫下你的評論
評論加載中...
作者其他優質文章