当前位置: 首页 > 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.lryc.cn/news/613168.html

相关文章:

  • 如何将服务器中的Docker镜像批量导出?
  • uat是什么
  • SIP - Centos 7 搭建freeswitch服务器
  • Linux第一阶段练习
  • Microsoft Office PowerPoint 制作简单的游戏素材
  • Sklearn 机器学习 数据降维PCA 自己实现PCA降维算法
  • 智能升级革命:Deepoc具身模型开发板如何让传统除草机器人拥有“认知大脑”
  • 【智能协同云图库】第六期:基于 百度API 和 Jsoup 爬虫实现以图搜图
  • RabbitMQ面试精讲 Day 15:RabbitMQ故障转移与数据恢复
  • 【数据结构】排序(sort) -- 交换排序(冒泡快排)
  • 大数据杀熟:技术阴影下的消费陷阱与破局之道
  • Dokcer创建中间件环境
  • RabbitMQ面试精讲 Day 13:HAProxy与负载均衡配置
  • 【Day 18】Linux-DNS解析
  • 香港网站服务器被占用的资源怎么释放?
  • 股指期货合约是个啥?怎么玩?
  • JVM 终止机制详解:用户线程与守护线程
  • WD6208资料和引脚图
  • MCU中的晶振(Crystal Oscillator)
  • 时间戳表示
  • 汽车娱乐信息系统域控制器的网络安全开发方案
  • 基于Ruby的IP池系统构建分布式爬虫架构
  • 基于 MATLAB 的 QPSK 调制、解调、通过高斯信道的误码率计算,并绘制误码率图和眼图、星座图
  • SurgRIPE 挑战赛:手术机器人器械位姿估计基准测试|文献速递-医学影像算法文献分享
  • 【源码】AndroidPlayer
  • 智能升级新纪元:基于Deepoc具身模型外拓开发板的除草机器人认知进化
  • 【图文教程】三步用Cpolar+JuiceSSH实现手机远程连接内网Linux虚拟机
  • Web开发模式 前端渲染 后端渲染 身份认证
  • 网页前端CSS实现表格3行平均分配高度,或者用div Flexbox布局
  • 网络安全等级保护(等保)2.0 概述