Android随笔-View绘制流程
2026/7/11 1:16:44 网站建设 项目流程

一、整体绘制流程

阶段触发方式作用
触发绘制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()

MeasureSpec

32位 int 值,高 2 位是 mode,低 30 位是 size:

Mode含义场景
EXACTLY0精确值match_parent/ 具体数值
AT_MOST1最大值wrap_content
UNSPECIFIED2无限制ScrollView 内部

核心代码

// View.measure()publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec){// 1. 缓存优化if(cacheIndex>=0&&...){// 使用缓存return;}// 2. 强制重新测量if(forceLayout||...){mPrivateFlags|=PFLAG_FORCE_LAYOUT;}// 3. 执行 onMeasureonMeasure(widthMeasureSpec,heightMeasureSpec);// 4. 存储测量结果mMeasuredWidth=...;mMeasuredHeight=...;}// ViewGroup.onMeasure()@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){// 默认调用 measureChildrenmeasureChildren(widthMeasureSpec,heightMeasureSpec);// 或自定义测量逻辑intwidth=0;intheight=0;for(inti=0;i<getChildCount();i++){Viewchild=getChildAt(i);measureChild(child,widthMeasureSpec,heightMeasureSpec);width+=child.getMeasuredWidth();height=Math.max(height,child.getMeasuredHeight());}setMeasuredDimension(width,height);}

注意点

✅ 正确做法❌ 常见错误
getMeasuredWidth()获取测量值onMeasuregetWidth()(此时 layout 未完成)
子 Viewmeasure()后调用忽略 MeasureSpec mode
setMeasuredDimension()设置结果测量后未调用setMeasuredDimension
MeasureSpec 解析父约束子 View 未 measure 就获取尺寸

三、Layout 布局阶段

流程

ViewRootImpl.performLayout() → decorView.layout() → 遍历子 View layout() → onLayout()

参数

layout(l, t, r, b):left, top, right, bottom 四个边界坐标

  • getWidth() = r - l
  • getHeight() = b - t

核心代码

// View.layout()publicvoidlayout(intl,intt,intr,intb){// 1. 判断是否需要重新布局booleanchanged=isLayoutModeOptical(...)?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 设置位置intchildLeft=getPaddingLeft();intchildTop=getPaddingTop();for(inti=0;i<getChildCount();i++){Viewchild=getChildAt(i);intcw=child.getMeasuredWidth();intch=child.getMeasuredHeight();child.layout(childLeft,childTop,childLeft+cw,childTop+ch);childLeft+=cw+mHorizontalSpacing;}}

注意点

✅ 正确做法❌ 常见错误
getWidth()/getHeight()获取布局后尺寸onMeasure中获取 width/height
onLayout中设置子 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: 保存画布层数finalintsaveCount=canvas.getSaveCount();// Step 3: 绘制内容 (子类实现)onDraw(canvas);// Step 4: 绘制子 ViewdispatchDraw(canvas);// Step 5: 绘制前景/装饰onDrawForeground(canvas);// Step 6: 恢复画布canvas.restoreToCount(saveCount);}

ViewGroup.dispatchDraw()

@OverrideprotectedvoiddispatchDraw(Canvascanvas){// 使用 for 循环或缓存finalintchildrenCount=mChildrenCount;finalView[]children=mChildren;for(inti=0;i<childrenCount;i++){Viewchild=children[i];if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE||...){drawChild(canvas,child,drawingTime);}}}// 子 View 绘制protectedbooleandrawChild(Canvascanvas,Viewchild,longdrawingTime){returnchild.draw(canvas,this,drawingTime);}

注意点

✅ 优化建议❌ 常见错误
避免在onDraw创建对象(会触发 GC,掉帧)onDrawnew Paint()/new Path()
使用 Path/Rect 缓存频繁调用invalidate()
使用Canvas.clipRect裁剪在子线程调用 invalidate
复杂绘制使用 SurfaceView/TextureView过度绘制 (Overdraw)
开启硬件加速

五、关键要点总结

measure 确定尺寸 → layout 确定位置 → draw 绘制到 Canvas → SurfaceFlinger 合成送显

阶段核心方法输出结果注意事项
MeasureonMeasure()mMeasuredWidth/HeightgetMeasuredWidth(),必须setMeasuredDimension()
LayoutonLayout()mLeft/mTop/mRight/mBottomgetWidth()/getHeight(),考虑 padding
DrawonDraw()像素渲染到 Canvas避免创建对象,使用缓存

六、自定义 View 完整示例

publicclassCustomViewextendsView{privatePaintpaint;privateRectrect;publicCustomView(Contextcontext){super(context);init();}privatevoidinit(){// 在构造函数初始化,不在 onDraw 中创建paint=newPaint(Paint.ANTI_ALIAS_FLAG);paint.setColor(Color.RED);rect=newRect();}@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){super.onMeasure(widthMeasureSpec,heightMeasureSpec);// 解析 MeasureSpecintwidthMode=MeasureSpec.getMode(widthMeasureSpec);intwidthSize=MeasureSpec.getSize(widthMeasureSpec);intwidth=widthMode==MeasureSpec.EXACTLY?widthSize:200;intheight=MeasureSpec.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);}}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询