今天是粗谈程2028年4月26日,天气晴,绘制和绘我请了一天假在家陪女儿。任务 正在陪女儿画画的制流我,被女儿问到: ?粗谈程?:“爸爸,妈妈说你的绘制和绘工作是可以把我们想到的东西变到手机上,是任务这样吗?” ??:“对呀,厉害吧~” ?制流?:“那你可以把我们家的小狗狗变到手机上吗?” ??:“当然可以了,不过手机是粗谈程很笨的东西,必须我们把所有的绘制和绘规则写好,他才能听我们的任务话~” ??:“什么规则呀“ 你看,手机屏幕只有这么大,制流所以我们先要确定狗狗的粗谈程大小,该画多大的绘制和绘狗狗,可以画多大的任务狗狗。 这就是测量的过程。 接着,我们要确定狗狗放在哪里,香港云服务器左上角还是中间还是右下角? 这就是布局的过程。 最后,我们就要画出狗狗的样子,是斑点狗还是大狼狗,是小白狗还是小黑狗。 这就是绘画的过程。 所以,在手机上变出一只狗狗,或者变出任何一个东西都需要三个步骤: 绘制任务的来源 把视线拉回到成年人的世界。 上篇文章说到,当有绘制任务的时候,会将这个任务交给Choreographer,然后再等下一个VSync信号来的时候,执行到ViewRootImpl的performTraversals方法。 那么这个任务到底从何而来呢?回顾下Activity的显示过程: 所以在第二步addView方法中,肯定进行了与View绘制有关的操作: 在addView方法中,创建了ViewRootImpl,执行了setView方法,在这里调用了requestLayout方法开始了View的绘制工作。 所以这里就是Activity显示界面所做的第一次绘制来源。 那后续界面上的元素改变带来的绘制呢? 首先看看在View中调用requestLayout方法会怎么绘制,比如TextView.setText,最后就会执行到requestLayout 精简之后的代码,主要干了两件事: 1、设置两个标志位,PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED。 2、执行父View的requestLayout方法。 这里的标志位暂且按下不表,待会就会遇到。站群服务器从第二点可以看到View会一直向上执行requestLayout方法,而顶层的View就是DecorView,DecorView的parent就是ViewRootImpl。 所以最后还是执行到了ViewRootImpl的requestLayout方法,开始整个View树的 测量、布局、绘画。 其中,mLayoutRequested字段设置为true,这是第二个标志位,待会也会遇到。 但是这有点奇怪哦?我一个View改变了,为什么整个界面的View树都需要重新绘制呢? 这是因为每个子View直接或多或少都会产生联系,比如一个RelativeLayout,一个View在TextView的右边,一个View在TextView的下面。 那么当TextView长度宽度变化了,那么其他的View自然也需要跟着变化,所以就必须整个View树进行重新绘制,保证布局的完整性。 还有一种触发绘制的情况就是View.invalidate/postInvalidate,postInvalidate一般用于子线程,最后也会调用到invalidate方法,就不单独说了。 invalidate方法一般用于View内部的重新绘画,比如同样是TextView.setText,也会触发invalidate方法。 可以看到,这里调用了invalidateInternal方法,并且传入了可绘制的区域,最后调用了父view的invalidateChild方法。 一个dowhile循环,不断调用父View的invalidateChildInParent方法。 也就是会执行ViewGroup的invalidateChildInParent,最后再执行ViewRootImpl的invalidateChildInParent方法,我们就直接看ViewRootImpl: 完事,果不其然,又到了scheduleTraversals绘制方法。 (这其中还有很多关于Dirty区域的绘制和转换我省略了,Dirty区域就是需要重新绘图的区域) 那invalidate和requestLayout有什么区别呢?继续研究scheduleTraversals方法。 接下来就看看peformTraversals方法是怎么触发到三大绘制流程的。 我只保留了与三大绘制流程相关的直接代码,可以看到: 1、测量过程的前提是layoutRequested为true,与mLayoutRequested有关。 2、布局过程的前提是didLayout,也与mLayoutRequested有关。 3、绘画过程的前提是!cancelDraw。 而mLayoutRequested字段是在requestlayout方法中进行设置的,invalidate方法中并没有设置。所以我们可以初步断定,只有requestLayout方法才会执行到onMeasure和onLayout。 在measure方法中,我们判断了两个字段forceLayout和needsLayout,当其中有一个为true的时候,才会继续执行onMeasure。其中forceLayout字段代表的是mPrivateFlags标志位是不是PFLAG_FORCE_LAYOUT。 PFLAG_FORCE_LAYOUT?是不是有点熟悉。刚才在View.requestLayout方法中,就对每个View都设置了这个标志,所以才能触发到onMeasure进行测量。 所以requestLayout方法通过这个标志位 PFLAG_FORCE_LAYOUT,使每个子View都能进入到onMeasure流程。 可以看到在layout方法中,是通过PFLAG_LAYOUT_REQUIRED标记来决定是否执行onLayout方法,而这个标记是在onMeasure方法执行之后设置的。 说明了只要onMeasure方法执行了,那么onLayout方法肯定也会执行,这两个方法是兄弟伙的关系,有你就有我。 先看第二步draw(boolean fullRedrawNeeded)方法: 在该方法中,判断了dirty是否为空,只有不为空的话才会继续执行下去。dirty是什么?刚才也说过,就是需要重绘的区域。 而我们调用invalidate方法的目的就是向上传递dirty区域,最终生成屏幕上需要重绘的dirty,requestLayout方法中并没有对dirty区域进行设定。 继续看draw(Canvas canvas)方法,注释还是比较清晰的,一共分为了六步: 而我们常用的onDraw就是用于绘制内容。 到此,View的绘制大体流程就结束了。 当然,其中还有大量细节,比如具体的绘制流程、需要注意的细节、自定义View实现等等,我们后面慢慢说道。 之前我们的问题,现在也可以解答了,就是绘制的两个请求:requestLayout和invalidate区别是什么? 所以如果某个元素的改变涉及到宽高布局的改变,就需要执行requestLayout()。如果某个元素之需要内部区域进行重新绘制,就执行invalidate(). 如果都需要,就先执行requestLayout(),在执行invalidate(),比如TextView.setText()。 参考 https://www.jianshu.com/p/e79a55c141d6 https://juejin.cn/post/6904518722564653070 本文转载自微信公众号「码上积木」,可以通过以下二维码关注。转载本文请联系码上积木公众号。前言
简述绘制流程
第一次界面绘制
View.requestLayout
View.invalidate/postInvalidate
peformTraversals
测量(measureHierarchy)
private boolean measureHierarchy() { childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); return windowSizeMayChange; } private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } public final void measure(int widthMeasureSpec, int heightMeasureSpec) { final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); if (forceLayout || needsLayout) { // first clears the measured dimension flag onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } } 布局(performLayout)
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { final View host = mView; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); } public void layout(int l, int t, int r, int b) { if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); } } 绘画(performDraw)
private void performDraw() { boolean canUseAsync = draw(fullRedrawNeeded); } private boolean draw(boolean fullRedrawNeeded){ if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { return false; } } return useAsyncReport; } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { mView.draw(canvas); return true; } 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 views content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed drawBackground(canvas); // Step 2, save the canvas layers canvas.saveUnclippedLayer.. // Step 3, draw the content onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers canvas.drawRect.. // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); } 总结