Android视图状态以及重绘

一、视图状态(View States)

1. 五种核心状态
状态 作用 修改方法 特点
enabled 视图是否响应交互 setEnabled(boolean) 禁用状态下不响应onTouch事件
focused 视图是否获得焦点 requestFocus() 需同时满足focusable和focusableInTouchMode
window_focused 视图所在窗口是否在前台 系统自动维护 应用无法直接修改
selected 视图是否被选中 setSelected(boolean) 同一界面允许多个视图同时选中
pressed 视图是否被按下 setPressed(boolean) 通常由系统自动设置点击状态
2. 状态变更响应流程

关键源码解析

  1. 状态变更入口

    java 复制代码
    // View.java
    protected void drawableStateChanged() {
        Drawable d = mBackground;
        if (d != null && d.isStateful()) {
            d.setState(getDrawableState()); // 传递新状态给Drawable
        }
    }
  2. 状态匹配原理

    java 复制代码
    // StateListDrawable.java
    protected boolean onStateChange(int[] stateSet) {
        int idx = findStateIndex(stateSet); // 匹配selector中对应的item
        return selectDrawable(idx); // 切换Drawable
    }
  3. 触发重绘

    java 复制代码
    // StateListDrawable.java
    public boolean selectDrawable(int idx) {
        // ...更新Drawable
        invalidateSelf(); // 关键重绘触发
    }

二、重绘机制(View Invalidation)

1. 两种重绘方式对比
方法 触发流程 应用场景 性能影响
invalidate() 仅重走draw()流程 内容变化但尺寸不变(如文字/颜色) 低开销,局部刷新
requestLayout() 完整measure-layout-draw 视图尺寸/结构变化(如添加子View) 高开销,全局重新布局
2. invalidate() 核心流程

源码关键路径

java 复制代码
// ViewRootImpl.java
void scheduleTraversals() {
    sendEmptyMessage(DO_TRAVERSAL); // 发送异步消息
}

public void handleMessage(Message msg) {
    if (msg.what == DO_TRAVERSAL) {
        performTraversals(); // 最终入口
    }
}

private void performTraversals() {
    // 根据标记位决定流程
    if (!mLayoutRequested) {
        // 仅执行draw流程
        performDraw();
    }
}
3. 性能优化要点
  1. 减少重绘范围

    java 复制代码
    // 只刷新局部区域
    public void invalidate(Rect dirty) {
        // 计算脏区域并传递
    }
  2. 避免过度重绘

    • 使用View.setWillNotDraw(true)跳过无内容视图

    • 合并状态变更(避免连续多次invalidate)


三、常见问题

Q1:按下按钮时背景图切换的完整流程?

A

  1. 状态变更View.setPressed(true)更新状态数组

  2. 通知DrawabledrawableStateChanged()调用StateListDrawable.setState()

  3. 匹配资源StateListDrawable.onStateChange()查找对应状态图片

  4. 触发重绘selectDrawable()invalidateSelf()View.invalidate()

  5. 绘制执行 :递归至ViewRootImpl.scheduleTraversals() → 下一帧触发draw()流程

Q2:invalidate() 和 requestLayout() 的本质区别?

A

  • invalidate()

    • 仅设置DIRTY标记 → 触发draw()流程

    • 不重新测量/布局 → 适用于内容变化但尺寸不变场景

  • requestLayout()

    • 设置FORCE_LAYOUT标记 → 触发完整measure-layout-draw

    • 向父视图递归 → 可能引发全局重新布局

Q3:为什么StateListDrawable能自动切换图片?

A :核心机制是状态匹配+重绘触发

  1. res/drawable中定义<selector>状态映射

  2. onStateChange()用状态数组匹配最佳item下标

  3. selectDrawable()切换当前Drawable并调用invalidateSelf()

Q4:自定义View如何优化重绘性能?

A:三级优化策略:

  1. 减少区域

    java 复制代码
    // 只刷新变化区域
    invalidate(dirtyRect);
  2. 避免过度绘制

    • 覆写hasOverlappingRendering()返回false

    • 使用canvas.clipRect()限制绘制区域

  3. 复用资源

    • 预初始化Paint/Path等对象

    • 使用View.setLayerType(LAYER_TYPE_HARDWARE)启用硬件加速

Q5:解释下 scheduleTraversals() 中发送异步消息的意义?是否在主线程执行?

A

核心是通过异步消息+同步屏障确保UI更新的及时性

  1. 异步消息DO_TRAVERSAL 消息被标记为异步类型,优先于普通消息处理

  2. 同步屏障

    • 在消息队列插入屏障,阻塞后续同步消息

    • 仅允许异步的UI更新消息通过

  3. 主线程执行

    • 消息最终由 ViewRootImplHandler 在主线程处理

    • 调用 performTraversals() 执行完整的视图树遍历

  4. 设计目的

    • 解决UI更新被业务消息阻塞的问题

    • 保证16ms内完成绘制(60Hz刷新率)

使用代码证明主线程执行

performTraversals() 中可检查线程:

java 复制代码
void performTraversals() {
    if (Thread.currentThread() != mThread) {
        throw new RuntimeException("Must be on UI thread!");
    }
    // ...measure/layout/draw...
}

其中 mThreadViewRootImpl 创建时的主线程。

Q6:View.postInvalidate() 和 invalidate() 区别?

A

维度 invalidate() postInvalidate()
调用线程 仅UI线程 任意线程
内部实现 直接操作视图树 通过Handler转发到UI线程
适用场景 视图内部状态变更 后台线程触发的UI更新

四、总结

Q:请解释Android视图状态变更如何触发界面更新?

A

整个过程分为四个关键阶段:

  1. 状态变更

    • 调用setPressed()/setSelected()等方法改变视图状态

    • 更新视图内部的mDrawableState状态数组

  2. Drawable响应

    • 触发drawableStateChanged()回调

    • StateListDrawable通过onStateChange()匹配新状态对应的Drawable资源

  3. 重绘调度

    • 调用invalidateSelf() → 触发View.invalidate()

    • 通过ViewParent链递归至ViewRootImpl

    • 通过scheduleTraversals()异步调度重绘

  4. 绘制执行

    • 下一帧触发performTraversals()

    • 根据标记位仅执行draw流程(measure/layout跳过)

    • 调用View.draw()Drawable.draw()渲染新状态对应的图片

性能优化要点

  • 优先使用invalidate(Rect)局部刷新

  • 复杂动画启用硬件加速(LAYER_TYPE_HARDWARE

  • 避免在draw()中创建对象

相关推荐
代龙涛2 小时前
WordPress page.php 页面模板与自定义模板使用方法
android·开发语言·php
爱莉希雅&&&3 小时前
zabbix快速搭建和使用
android·linux·数据库·zabbix·监控
zfoo-framework5 小时前
理解kotlin limitedParallelism(1)与Actor模型
android·开发语言·kotlin
2401_865721337 小时前
御网杯初赛2026
android
葱段7 小时前
【Compose】输入框(TextField)点击空白处失焦并关闭软键盘
android
刮风那天9 小时前
Android Framework 核心架构图
android
__Witheart__9 小时前
3588 安卓编译空间不足报错
android
aaajj10 小时前
【Android】手机屏幕劫持防护
android·智能手机
写做四月一日的四月一日10 小时前
在安卓手机上安装小龙虾openclaw并配置QQ机器人接入
android·人工智能
流星白龙10 小时前
【MySQL高阶】6.MySQL数据目录,日志
android·mysql·adb