一句话说透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 全量走
  • 优化层级少嵌套,流畅体验不用愁!
相关推荐
写点啥呢8 分钟前
Android Studio 多语言助手插件:让多语言管理变得简单高效
android·ai·ai编程·多语言
泥嚎泥嚎2 小时前
【Android】给App添加启动画面——SplashScreen
android·java
全栈派森2 小时前
初见 Dart:这门新语言如何让你的 App「动」起来?
android·flutter·ios
q***98522 小时前
图文详述:MySQL的下载、安装、配置、使用
android·mysql·adb
恋猫de小郭3 小时前
Dart 3.10 发布,快来看有什么更新吧
android·前端·flutter
恋猫de小郭4 小时前
Flutter 3.38 发布,快来看看有什么更新吧
android·前端·flutter
百锦再10 小时前
第11章 泛型、trait与生命周期
android·网络·人工智能·python·golang·rust·go
会跑的兔子11 小时前
Android 16 Kotlin协程 第二部分
android·windows·kotlin
键来大师11 小时前
Android15 RK3588 修改默认不锁屏不休眠
android·java·framework·rk3588
江上清风山间明月14 小时前
Android 系统超级实用的分析调试命令
android·内存·调试·dumpsys