当前位置: 首页 > news >正文

Android视图状态以及重绘

一、视图状态(View States)

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

关键源码解析

  1. 状态变更入口

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

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

    // StateListDrawable.java
    public boolean selectDrawable(int idx) {// ...更新DrawableinvalidateSelf(); // 关键重绘触发
    }

二、重绘机制(View Invalidation)

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

源码关键路径

// 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. 减少重绘范围

    // 只刷新局部区域
    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. 减少区域

    // 只刷新变化区域
    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. 主线程执行

    • 消息最终由 ViewRootImpl 的 Handler 在主线程处理

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

  4. 设计目的

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

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

使用代码证明主线程执行
在 performTraversals() 中可检查线程:

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

其中 mThread 即 ViewRootImpl 创建时的主线程。

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()中创建对象

http://www.xdnf.cn/news/1254961.html

相关文章:

  • 快速开发实践
  • 内网穿透原理和部署教程
  • 【Kubernetes】部署 kube-bench 实现 K8s 最佳实践
  • tcpdump问题记录
  • Linux下动态库链接的详细过程
  • 【数据结构初阶】--排序(五)--计数排序,排序算法复杂度对比和稳定性分析
  • Python Socket 脚本深度解析与开发指南
  • MySQL梳理四:事务日志机制和多版本并发控制(MVCC)
  • SpringMvc的原理深度剖析及源码解读
  • 前端页面直接生成PDF下载文件
  • “物联网+职业本科”:VR虚拟仿真实训室的发展前景
  • 物联网架构全解析:华为“1+2+1”与格行随身WiFi,技术如何定义未来生活?
  • 基于开源AI智能名片链动2+1模式S2B2C商城小程序的微商产品经营策略研究
  • 技术优势铸就行业标杆:物联网边缘计算网关凭何引领智能变革?
  • 008 前端vue
  • Spring AOP动态代理核心原理深度解析 - 图解+实战揭秘Java代理设计模式
  • RabbitMQ-日常运维命令
  • 嵌入式硬件中MOSFET基本原理与实现
  • python函数--python010
  • Redis中间件(三):Redis存储原理与数据模型
  • 小红书开源多模态视觉语言模型DOTS-VLM1
  • ubuntu 2024 安装拼音输入法
  • VC6800智能相机:赋能智能制造,开启AI视觉新纪元
  • 【关于Java 8 的新特性】
  • 语言模型(LM):n-gram模型原理与困惑度(Perplexity)计算详解
  • 38.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--增加日志记录器
  • 嵌入式C语言编程:策略模式、状态模式和状态机的应用
  • 首个!3D空间推理框架3D-R1:融合强化学习、推理链、动态视角,实现7大任务SOTA!
  • LabVIEW注册表操作
  • 如何在 VS Code 中进行 `cherry-pick`