一句话说透Android里面的View的绘制流程和实现原理

一句话总结
View 的绘制流程就像盖房子:先量尺寸(Measure)→ 再摆位置(Layout)→ 最后刷墙装修(Draw)。整个过程由"包工头" ViewRootImpl 指挥,保证按时(16ms/帧)完工!


一、绘制流程三步走

1. Measure(量尺寸)

  • 核心任务:确定每个 View 的宽高。

  • 规则传递 :父 View 根据自身规则(如 LinearLayout 的权重)向子 View 传递 MeasureSpec(测量规格),包含:

    • EXACTLY :精确尺寸(如 100dp)。
    • AT_MOST :最大尺寸(如 match_parent)。
    • UNSPECIFIED :随便你(如 wrap_content,但实际可能受父容器限制)。
  • 代码入口View.measure()onMeasure()

示例

arduino 复制代码
// 自定义 View 重写 onMeasure  
@Override  
protected void onMeasure(int widthSpec, int heightSpec) {  
    int width = calculateWidth(widthSpec); // 根据父容器的限制计算自己的宽  
    int height = calculateHeight(heightSpec);  
    setMeasuredDimension(width, height); // 必须调用!  
}  

2. Layout(摆位置)

  • 核心任务:确定 View 在屏幕上的位置(四个坐标:left, top, right, bottom)。
  • 父控件的责任 :遍历子 View,调用 child.layout() 设置其位置。
  • 代码入口View.layout()onLayout()

示例

java 复制代码
// 自定义 ViewGroup 重写 onLayout  
@Override  
protected void onLayout(boolean changed, int l, int t, int r, int b) {  
    for (int i = 0; i < getChildCount(); i++) {  
        View child = getChildAt(i);  
        child.layout(childLeft, childTop, childRight, childBottom);  
    }  
}  

3. Draw(刷墙装修)

  • 核心任务:将 View 的内容绘制到屏幕上。

  • 绘制顺序

    1. 绘制背景(drawBackground())。
    2. 绘制自身内容(onDraw())。
    3. 绘制子 View(dispatchDraw())。
    4. 绘制装饰(如滚动条、前景)。
  • 代码入口View.draw()onDraw()

示例

typescript 复制代码
// 自定义 View 重写 onDraw  
@Override  
protected void onDraw(Canvas canvas) {  
    super.onDraw(canvas);  
    canvas.drawCircle(x, y, radius, paint); // 画个圆  
}  

二、绘制原理:幕后的大佬们

1. ViewRootImpl:总包工头

  • 职责

    • 管理整个 View 树的绘制流程(performTraversals())。
    • 通过 Choreographer 监听 VSync 信号(屏幕刷新脉冲),确保每帧在 16ms 内完成。

2. Choreographer:节奏大师

  • 作用:协调输入、动画、绘制事件,保证按 VSync 信号同步执行。
  • 卡顿检测:如果一帧超时(>16ms),触发掉帧(Jank)。

3. SurfaceFlinger:刷墙队

  • 职责:将各窗口(Surface)的绘制结果合成,最终显示到屏幕。
  • 双缓冲机制:避免绘制过程中出现闪烁(Back Buffer 绘制,Front Buffer 显示)。

三、触发绘制的两种方式

方法 作用 触发流程
invalidate() 标记脏区域,触发重绘(Draw 阶段) 只走 draw() 流程
requestLayout() 强制重新测量和布局(可能重绘) 触发 measure()layout()draw()

示例

scss 复制代码
// 点击按钮后更新 View  
button.setOnClickListener {  
    myView.updateContent(); // 修改数据  
    myView.invalidate();    // 触发重绘  
}  

四、优化绘制的实战技巧

1. 减少层级嵌套

  • 问题:嵌套过深导致多次测量(如 RelativeLayout 嵌套 LinearLayout)。
  • 解决 :使用 ConstraintLayout 替代,扁平化布局。

2. 避免过度绘制

  • 检测工具:开发者选项 → "显示过度绘制区域"。

  • 优化方案

    • 移除不必要的背景(如父容器已设背景,子 View 无需重复)。
    • 使用 canvas.clipRect() 限制绘制区域。

3. 离线绘制

  • 场景:复杂静态内容(如图表)。
  • 方案 :使用 BitmapCanvas 预渲染,直接绘制缓存。

五、总结口诀

  • 绘制流程三步走,测量布局再刷墙
  • ViewRoot 总指挥,Choreographer 卡节奏
  • invalidate 重绘,requestLayout 全量走
  • 优化层级少嵌套,流畅体验不用愁!
相关推荐
飞猿_SIR1 小时前
Android Exoplayer 实现多个音视频文件混合播放以及音轨切换
android·音视频
HumoChen992 小时前
GZip+Base64压缩字符串在ios上解压报错问题解决(安卓、PC模拟器正常)
android·小程序·uniapp·base64·gzip
沙振宇6 小时前
【HarmonyOS】ArkTS开发应用的横竖屏切换
android·华为·harmonyos
橙子199110168 小时前
Kotlin 中的作用域函数
android·开发语言·kotlin
zimoyin8 小时前
Kotlin 懒初始化值
android·开发语言·kotlin
枣伊吕波8 小时前
第六节第二部分:抽象类的应用-模板方法设计模式
android·java·设计模式
萧然CS9 小时前
使用ADB命令操作Android的apk/aab包
android·adb
_extraordinary_13 小时前
MySQL 事务(二)
android·数据库·mysql
鸿蒙布道师17 小时前
鸿蒙NEXT开发动画案例5
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
橙子199110161 天前
在 Kotlin 中什么是委托属性,简要说说其使用场景和原理
android·开发语言·kotlin