Android随笔-View绘制流程
一、整体绘制流程阶段触发方式作用触发绘制requestLayout()/invalidate()/postInvalidate()标记 View 需要重绘加入 Choreographer 队列VSync 信号Choreographer.doFrame()16.6ms (60fps) 同步信号避免画面撕裂ViewRootImplperformTraversals()调度 measure → layout → draw 三大流程SurfaceFlinger合成 Layer送显到屏幕用户看到画面二、Measure 测量阶段流程ViewRootImpl.performMeasure() → decorView.measure() → 遍历子 View measure() → onMeasure()MeasureSpec32位 int 值高 2 位是 mode低 30 位是 sizeMode值含义场景EXACTLY0精确值match_parent/ 具体数值AT_MOST1最大值wrap_contentUNSPECIFIED2无限制ScrollView 内部核心代码// View.measure()publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec){// 1. 缓存优化if(cacheIndex0...){// 使用缓存return;}// 2. 强制重新测量if(forceLayout||...){mPrivateFlags|PFLAG_FORCE_LAYOUT;}// 3. 执行 onMeasureonMeasure(widthMeasureSpec,heightMeasureSpec);// 4. 存储测量结果mMeasuredWidth...;mMeasuredHeight...;}// ViewGroup.onMeasure()OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){// 默认调用 measureChildrenmeasureChildren(widthMeasureSpec,heightMeasureSpec);// 或自定义测量逻辑intwidth0;intheight0;for(inti0;igetChildCount();i){ViewchildgetChildAt(i);measureChild(child,widthMeasureSpec,heightMeasureSpec);widthchild.getMeasuredWidth();heightMath.max(height,child.getMeasuredHeight());}setMeasuredDimension(width,height);}注意点✅ 正确做法❌ 常见错误getMeasuredWidth()获取测量值在onMeasure中getWidth()此时 layout 未完成子 Viewmeasure()后调用忽略 MeasureSpec modesetMeasuredDimension()设置结果测量后未调用setMeasuredDimensionMeasureSpec 解析父约束子 View 未 measure 就获取尺寸三、Layout 布局阶段流程ViewRootImpl.performLayout() → decorView.layout() → 遍历子 View layout() → onLayout()参数layout(l, t, r, b)left, top, right, bottom 四个边界坐标getWidth() r - lgetHeight() b - t核心代码// View.layout()publicvoidlayout(intl,intt,intr,intb){// 1. 判断是否需要重新布局booleanchangedisLayoutModeOptical(...)?setOpticalFrame(l,t,r,b):setFrame(l,t,r,b);// 2. 如果位置变化或强制布局if(changed||...){// 3. 调用 onLayoutonLayout(changed,l,t,r,b);// 4. 标记布局完成mPrivateFlags~PFLAG_FORCE_LAYOUT;}}// ViewGroup.onLayout() (抽象子类必须实现)OverrideprotectedvoidonLayout(booleanchanged,intl,intt,intr,intb){// 遍历子 View 设置位置intchildLeftgetPaddingLeft();intchildTopgetPaddingTop();for(inti0;igetChildCount();i){ViewchildgetChildAt(i);intcwchild.getMeasuredWidth();intchchild.getMeasuredHeight();child.layout(childLeft,childTop,childLeftcw,childTopch);childLeftcwmHorizontalSpacing;}}注意点✅ 正确做法❌ 常见错误getWidth()/getHeight()获取布局后尺寸在onMeasure中获取 width/heightonLayout中设置子 View 位置layout 参数计算错误使用getMeasuredWidth()作为参考忘记处理 padding考虑 padding/margin子 View 未 measure 就 layout四、Draw 绘制阶段流程ViewRootImpl.performDraw() → draw() → onDraw() → dispatchDraw() → 遍历子View draw()Draw 六步曲// View.draw()publicvoiddraw(Canvascanvas){// Step 1: 绘制背景drawBackground(canvas);// Step 2: 保存画布层数finalintsaveCountcanvas.getSaveCount();// Step 3: 绘制内容 (子类实现)onDraw(canvas);// Step 4: 绘制子 ViewdispatchDraw(canvas);// Step 5: 绘制前景/装饰onDrawForeground(canvas);// Step 6: 恢复画布canvas.restoreToCount(saveCount);}ViewGroup.dispatchDraw()OverrideprotectedvoiddispatchDraw(Canvascanvas){// 使用 for 循环或缓存finalintchildrenCountmChildrenCount;finalView[]childrenmChildren;for(inti0;ichildrenCount;i){Viewchildchildren[i];if((child.mViewFlagsVISIBILITY_MASK)VISIBLE||...){drawChild(canvas,child,drawingTime);}}}// 子 View 绘制protectedbooleandrawChild(Canvascanvas,Viewchild,longdrawingTime){returnchild.draw(canvas,this,drawingTime);}注意点✅ 优化建议❌ 常见错误避免在onDraw创建对象会触发 GC掉帧onDraw中new Paint()/new Path()使用 Path/Rect 缓存频繁调用invalidate()使用Canvas.clipRect裁剪在子线程调用 invalidate复杂绘制使用 SurfaceView/TextureView过度绘制 (Overdraw)开启硬件加速五、关键要点总结measure 确定尺寸 → layout 确定位置 → draw 绘制到 Canvas → SurfaceFlinger 合成送显阶段核心方法输出结果注意事项MeasureonMeasure()mMeasuredWidth/Height用getMeasuredWidth()必须setMeasuredDimension()LayoutonLayout()mLeft/mTop/mRight/mBottom用getWidth()/getHeight()考虑 paddingDrawonDraw()像素渲染到 Canvas避免创建对象使用缓存六、自定义 View 完整示例publicclassCustomViewextendsView{privatePaintpaint;privateRectrect;publicCustomView(Contextcontext){super(context);init();}privatevoidinit(){// 在构造函数初始化不在 onDraw 中创建paintnewPaint(Paint.ANTI_ALIAS_FLAG);paint.setColor(Color.RED);rectnewRect();}OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){super.onMeasure(widthMeasureSpec,heightMeasureSpec);// 解析 MeasureSpecintwidthModeMeasureSpec.getMode(widthMeasureSpec);intwidthSizeMeasureSpec.getSize(widthMeasureSpec);intwidthwidthModeMeasureSpec.EXACTLY?widthSize:200;intheightMeasureSpec.getMode(heightMeasureSpec)MeasureSpec.EXACTLY?MeasureSpec.getSize(heightMeasureSpec):200;setMeasuredDimension(width,height);}OverrideprotectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){super.onLayout(changed,left,top,right,bottom);// 设置绘制区域rect.set(0,0,getWidth(),getHeight());}OverrideprotectedvoidonDraw(Canvascanvas){super.onDraw(canvas);// 使用缓存的 Paint 和 Rectcanvas.drawRect(rect,paint);}}