一句话说透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 全量走
  • 优化层级少嵌套,流畅体验不用愁!
相关推荐
消失的旧时光-194328 分钟前
Android 接入 Flutter(Add-to-App)最小闭环:10 分钟跑起第一个混合页面
android·flutter
城东米粉儿42 分钟前
android StrictMode 笔记
android
Zender Han1 小时前
Flutter Android 启动页 & App 图标替换(不使用任何插件的完整实践)
android·flutter·ios
童无极1 小时前
Android 弹幕君APP开发实战01
android
赛恩斯1 小时前
kotlin 为什么可以在没有kotlin 环境的安卓系统上运行的
android·开发语言·kotlin
于山巅相见1 小时前
【3588】Android动态隐藏导航栏
android·导航栏·状态栏·android11
乡野码圣1 小时前
【RK3588 Android12】开发效率提升技巧
android·嵌入式硬件
eybk2 小时前
Beeware生成安卓apk取得系统tts语音朗读例子
android
zhangphil3 小时前
Android图像显示,CPU的Skia与GPU的Vulkan高性能渲染系统
android