目录
一、View的绘制机制
1.1、View树的绘制流程
- 三大阶段:measure(测量)-> layout(布局)-> draw(绘制),整个过程在ViewRootImpl类的performTraversals()方法中展开
- 递归特性:类似树形递归过程,父视图通过measure和layout方法依次测量/摆放所有子视图
- 触发时机:当Activity获得用户焦点时,由Framework层从根节点开始处理绘制请求
1.2、measure方法
①、ViewGroup.LayoutParams
- 作用:指定视图高度和宽度的参数,每个视图必须包含
- 取值类型:
- 具体数值(如100dp)
- MATCH_PARENT(与父控件等大,不包括padding值)
- WRAP_CONTENT(自适应内容大小)
②、MeasureSpec
- 数据结构:32位int值,高2位表示测量模式(SpecMode),低30位表示尺寸大小
- 模式分类:
- UNSPECIFIED:父控件无约束(实际开发极少使用)
- EXACTLY:父容器确定精确尺寸(对应match_parent或具体数值)
- AT_MOST:父容器指定最大尺寸(对应wrap_content)
- 转换规则:系统会将View的LayoutParams根据父容器规则转换为对应的MeasureSpec
③、measure方法中重要的回调函数
- measure方法
- 调用链:measure() -> onMeasure() -> setMeasuredDimension()
- 强制约束:必须调用setMeasuredDimension()设置测量结果,否则会抛出illegalStateException
- 二次测量机制:当子视图尺寸不符合约束时,父视图会要求重新测量
- onMeasure方法
- 核心参数:widthMeasureSpec和heightMeasureSpec包含父容器的约束条件
- 自定义要点:继承View时必须重写该方法,并通过setMeasuredDimension()保存计算结果
- 测量流程:自上而下遍历视图树,父容器通过MeasureSpec和子视图的LayoutParams决定子视图尺寸
总结:measure方法开始于父控件ViewGroup,通过不断的遍历子控件的measure方法,然后根据ViewGroup的MeasureSpec和子View的LayoutParams来决定子视图的MeasureSpec测量规格。通过这个测量规格MeasureSpec来进一步获取到子View的测量的宽高,然后一层一层的向下传递,不断的保存整个父控件的测量宽高。整个measure测量的调用流程,就是一个树形的递归过程,为整个View树计算实际的大小。每一个View视图控件的实际的宽和高都是由父视图和它本身的LayoutParams所决定的。
1.3、layout方法
Layout的作用是 ViewGroup用来确定子元素的位置,当 ViewGroup的位置被确定后, 它在onLayout中会遍历所有的子元素并调用其 layout方法,在 layout方法中 onLayout 方法又会被调用。layout方法确定View 本身的位置,而 onLayout方法则会确定所有子元素的位置。
onLayout()方法:
- 位置基准:子视图位置都是相对于父视图的坐标系
- 实现要求:自定义ViewGroup必须实现onLayout()来摆放子视图
- 测量依赖:layout过程会使用measure阶段得到的尺寸(getMeasuredWidth/Height)
1.4、draw方法
Draw的作用是将 View 绘制到屏幕上面。
View 的绘制过程遵循 如下几步:
(1)绘制背景 background.draw(canvas)。
(2)绘制自己(onDraw)。
(3)绘制 children (dispatchDraw)。
(4)绘制装饰(onDraw ScrollBars)。
这里需要注意的是有两个容易混淆的方法:
- invalidate():
- 仅触发draw流程
- 视图尺寸未变化时不触发measure和layout
- requestLayout():
- 会触发measure和layout流程
- 不自动触发draw流程
- 常用于视图尺寸/方向发生变化时
总结:
- 完整流程:measure(计算尺寸)-> layout(确定位置)-> draw(实际绘制)
- 核心特点:
- 树形递归结构,从DecorView开始自上而下遍历
- 父容器通过MeasureSpec约束子视图
- 自定义View必须实现关键回调方法(onMeasure/onLayout)
二、事件分发机制
2.1、为什么会有事件分发机制
- 产生背景:安卓中的View以树形结构存在,多个View可能重叠在一起。当点击区域有多个View可响应时,系统需要确定事件分配对象。
- 核心问题:点击事件可能同时触发多个重叠View的响应,必须通过机制确定最终响应者。
- 解决方案:引入事件分发机制,通过层级传递和判断决定事件最终处理者。
- 典型场景:点击View1时,其父容器ViewGroupA和RootView也能响应,需要通过分发机制确定处理者。
2.2、三个重要的事件分发方法
- dispatchTouchEvent:
- 作为事件分发链的起点,决定事件由自身处理还是分发给子View
- 内部会调用onInterceptTouchEvent判断是否拦截
- 递归调用子View的dispatchTouchEvent
- onInterceptTouchEvent:
- 父控件在向子控件下发事件时,通过此方法决定是否拦截
- 返回true表示拦截,事件将不会传递给子View
- 仅存在于ViewGroup中,Activity和普通View无此方法
- onTouchEvent:
- 处理传递到View的触摸事件
- 处理四种基本手势:按下(ACTION_DOWN)、移动(ACTION_MOVE)、抬起(ACTION_UP)、取消(ACTION_CANCEL)
- 返回值决定是否消费事件(true表示消费)
- 特殊说明:
- Activity作为原始分发者不能拦截事件,否则会导致全屏无响应
- View作为最末端要么消费事件,要么回传,无需拦截
2.3、事件分发流程
- 正向传递流程:
- Activity → PhoneWindow(通过DecorView传递)
- DecorView → 最外层ViewGroup
- ViewGroup逐级向下传递至目标View
- 反向回传机制:
- 如果最终View未消费事件,事件会依次回传至Activity
- 只有所有层级都不处理时,事件才会被抛弃
- 采用责任链设计模式,保证处理灵活性和有序性
- 设计特点:
- 上层View既可拦截处理,也可询问子View
- 子View不处理时可回传给父View
- 形成完整的"分发-处理-回传"闭环
2.4、案例分析
- 场景:点击View1,父控件都不拦截
- 流程分析:
- Activity.dispatchTouchEvent → PhoneWindow
- PhoneWindow通过DecorView.dispatchTouchEvent传递
- RootView判断不拦截(onIntercept返回false)
- ViewGroupA同样不拦截,传递至View1
- View1.onTouchEvent返回true表示消费
- 回传过程:
- View1 → ViewGroupA.dispatchTouchEvent返回true
- 逐级回传至Activity
- 关键点:
- 每个层级的dispatchTouchEvent都获得最终返回值
- 消费标记通过返回值链式传递
- 整个过程体现责任链模式特点
三、ListView
3.1、什么是ListView
- 本质:ListView是一个能将数据集合以动态滚动方式展示到用户界面上的View控件
- 特点:
- 在Android所有常用控件中属于用法较复杂的
- 专门用于处理内容元素多且屏幕无法全部展示的情况
- 通过手指滑动即可查看超出屏幕部分的内容
- 应用场景:适合以列表形式展示大量数据内容
3.2、ListView的适配器模式
- 作用:作为数据源和ListView之间的桥梁
- 核心思想:
- 实现数据和视图的分离(MVC设计模式)
- ListView只关心在哪个position显示哪个View
- 不直接处理数据源,通过统一接口的Adapter处理
- 工作流程:
- 为每个数据项创建对应的View
- 将View交给ListView显示
- 保证不同数据源都能适配ListView的显示需求
- 关键方法:
- getCount():返回数据项总数
- getView():绘制每个item的视图
- 绘制原理:
- 先调用getCount获取数据长度
- 根据长度循环调用getView绘制每个item
- 有多少个item就会调用多少次getView
3.3、ListView的recycleBin机制
- 作用:解决ListView显示大量数据时的内存问题
- 核心思想:像回收站一样缓存不可见的item视图
- 重要变量:
- mActiveViews:存储当前屏幕上可见的View
- mScrapViews:二维数组,存储所有废弃的View列表
- mCurrentScrap:当前被划出屏幕的废弃View列表
- 关键方法:
- setViewTypeCount():为不同类型数据项建立回收机制
- fillActiveViews():将指定元素存储到mActiveViews数组
- getActiveView():获取屏幕上显示的View(获取后该位置会置null)
- addScrapView():缓存刚被划出屏幕的View
- 工作流程:
- item滑出屏幕时存入RecycleBin
- 新item进入屏幕时从RecycleBin获取可复用的View
- 通过convertView参数实现View的复用
3.4、ListView的优化
①、convertView
- 原理:利用convertView参数缓存已滑出屏幕的item视图
- 实现方式:
- 判断convertView是否为null
- 不为null时直接复用,避免重复创建View
- 初始显示时convertView为null,需创建新View
②、ViewHolder
- 作用:避免频繁调用findViewById
- 实现要点:
- 定义静态内部类ViewHolder
- 存储item中的控件引用
- 通过setTag/getTag方法存取ViewHolder
- 优势:减少View树遍历开销,提升性能
③、三级缓存
- 应用场景:主要用于图片加载优化(内存、磁盘、网络,使用Picasso或Glide等三方库加载)
- 注意事项:
- getView中避免耗时操作
- 保证滑动流畅性
④、滑动事件监听
- 优化策略:
- 监听滑动状态
- 滑动停止后再加载图片
- 其他优化点:
- 避免item布局中使用半透明元素
- 开启硬件加速提升绘制性能
四、RecycleView
4.1、RecyclerView和ListView的区别
①、布局管理器
- RecyclerView使用LayoutManager管理布局,支持线性布局、网格布局等更为复杂的布局方式
- ListView默认只支持垂直线性布局
②、视图复用
- RecyclerView默认使用ViewHolder模式实现视图复用
- ListView没有强制使用ViewHolder模式,需要自行实现复用
③、Item装饰和动画
- RecyclerView通过ItemDecoration和ItemAnimator提供装饰和动画支持,实现分隔线添加和列表动画更加简单
- ListView需要自定义实现
④、缓存机制
- RecyclerView是四级缓存,缓存效率更高,且缓存的是ViewHolder
- ListView是两级缓存,且缓存的是View
⑤、数据更新
RecyclerView提供了notifyItemInserted()、notifyItemRemoved()和notifyItemChanged()等局部更新方法
⑥、性能优化
RecyclerView通过视图复用、预加载、批量处理等机制使得滑动时具有更加高效的性能
4.2、RecyclerView的复用机制
4.2.1、RecyclerView的四级缓存
RecyclerView的Recycler类定义了四级缓存机制。
- AttachedScrap和ChangedScrap同属第一级缓存,其特点是缓存的ViewHolder在复用时无需重新绑定数据,因为RecyclerView认为这些ViewHolder会再次出现在屏幕上。
- CachedViews属于第二级缓存,存储滑动出屏幕的ViewHolder,默认容量为2,可通过setItemCacheSize方法扩容。
- ViewCacheExtension是第三级缓存,允许开发者自定义ViewHolder缓存策略。
- RecycledViewPool是第四级缓存,当CachedViews容量不足时,ViewHolder会被转移至此,默认每个ViewType对应容量为5,可通过setMaxRecycledViews方法调整。
四级缓存作用场景差异明显:一级缓存(AttachedScrap/ChangedScrap)的ViewHolder保留完整数据状态;二级缓存(CachedViews)需校验位置一致性;三级缓存(ViewCacheExtension)需开发者自定义;四级缓存(RecycledViewPool)按ViewType分类存储。
4.2.2、RecyclerView复用机制
复用机制执行流程为:
- 一级缓存查询:优先检查AttachedScrap和ChangedScrap
- 二级缓存查询:检索CachedViews并校验位置一致性
- 三级缓存查询:查找ViewCacheExtension(通常为空)
- 四级缓存查询:从RecycledViewPool按ViewType匹配
- 最终创建:若各级缓存均未命中,则调用Adapter.createViewHolder新建
思考总结:
- 插拔式设计适用于大型框架设计,比如阿里开源的VLayout是对layout manager的高度定制。
- RecyclerView性能优化:为列表项指定确切宽高,可减少onMeasure阶段的测量开销。
- 列表刷新优化:优先使用notifyItemChange而非全局notifyDataSetChanged,结合差分刷新工具可显著提升性能。
OK,今天的内容就先到这里了,下期继续哦!