[TOC]
# View的绘制流程
## View的测量
1、不管是View还是ViewGroup,每个人都会有一个父布局给过来的MeasureSpec,用来设置自己的宽高
* 测量模式为AT\_MOST或EXACTLY时,测量宽高值为MeasureSpec中的specSize
* 测量模式为UNSPECIFIED时,测量宽高值为默认值
2、View在onMeasure方法中,根据从父布局ViewGroup那里得到的MeasureSpec,来设置自身的宽高。
3、同样,ViewGroup也是在onMeasure方法中设置自己的宽高。
4、ViewGroup在onMeasure方法中设置自己宽高之前会多做一步,循环为所有子View设置MeasureSpec,并调用每个子View的measure方法,让子View测量自己的宽高。
5、ViewGroup根据自己的MeasureSpec、padding以及子View的LayoutParams,为子View设置MeasureSpec。
* 子View设置为固定宽高时,子View的specMode为:Exactly,specSize为:LayoutParams中设置的值,子View的测量宽高为LayoutParams中设置的值。
* 子View设置为match\_parent时,子View的specMode为:等同ViewGroup的specMode,specSize为:ViewGroup的剩余空间,子View的测量宽高为ViewGroup的剩余空间。
* 子View设置为wrap\_content时,子View的specMode为:AT\_MOST,specSize为:ViewGroup的剩余空间,子View的测量宽高为ViewGroup的剩余空间。
6、自定义ViewGroup通常会重写onMeasure方法,在onMeasure方法中为所有子View生成MeasureSpec并测量子View宽高。
## View的布局
1、View的layout方法中,调用setFrame方法为View自己指定位置,调用onLayout方法为所有的childView指定位置
2、setFrame方法为mLeft、mTop、mRight、mBottom几个属性赋值,来确定View自己相对于父布局的位置
3、ViewGroup的onLayout方法是一个抽象方法,子类需要实现此方法,并在实现中根据ViewGroup的布局逻辑计算出每个childView的位置,然后调用childView的layout方法进行子View布局
## View的绘制
1、View的draw方法按流程分别绘制背景、content、childView、前景、默认焦点等。其中onDraw方法绘制View的内容。
2、如果是ViewGroup,会调用dispatchDraw方法绘制childView。
3、ViewGroup的dispatchDraw方法,会调用所有的childView的draw方法进行绘制子View。
4、可通过重写View的onDraw方法,拿到Canvas来绘制想要绘制的内容
## 附
ViewGroup为childView生成MeasureSpec的规则:
* View配置固定宽高时,View的SpecMode总是EXCATLY模式,SpecSize总是LayoutParams中设置的值
* View配置match\_parent时,ViewGroup是什么模式,View就是什么模式,SpecSize是ViewGroup剩余的空间
* View配置wrap\_content时,View的SpecMode总是AT_MOST模式,SpecSize总是ViewGroup的剩余空间
注意:
当View配置wrap\_content时,View的SpecMode总是为AT_MOST,SpecSize总是为ViewGroup的剩余空间。View在根据MeasureSpec设置测量宽高时,就会设置成ViewGroup的剩余空间,与期望的wrap\_content不符。
解决方案为自定义View时重写onMeasure方法,在View的SpecMode为AT_MOST时,为View指定一个宽高。ImageView、TextView等都是如此操作,可参考其源码。
# 触摸事件分发机制
## Activity的分发
1、事件先分发给PhoneWindow,PhoneWindow不消费则传给Activity的onTouchEvent方法。
2、PhoneWindow传给DecorView,DecorView传给根布局ViewGroup。
## ViewGroup的分发
1、首先调用onInterCeptTouchEvent方法,判断ViewGroup自己是否需要,需要则拦截,拦截后就不会传递给childView。
2、拦截后,调用ViewGroup自己的onTouchEvent方法。
3、不拦截则传递给childView,按视图层次结构依次传递下去。
4、最终的那个childView也不需要时,事件会进行回传,依次调用前面每一层的onTouchEvent方法。
## View的分发
1、View会依次调用onTouchListener、onTouchEvent、onLongClickListener、onClickListener。
2、在onTouchListener、onTouchEvent中可决定是否消费事件,不消费,事件则开始回传。
3、注册了onLongClickListener、onClickListener及设置了Clickable等,View就会消费事件。
## 伪代码
1、ViewGroup分发的伪代码
```java
// 返回值代表是否将事件消费掉
public boolean dispatchTouchEvent(MotionEvent event) {
// 默认状态为未消费过
boolean result = false;
// 如果没有拦截(ViewGroup一般不会进行拦截,可点击的ViewGroup除外)
if (!onInterceptTouchEvent(event)) {
// 则交给childView
result = child.dispatchTouchEvent(event);
}
// 如果事件没有被childView消费
if (!result) {
// 则调用自身 onTouchEvent()
result = onTouchEvent(event);
}
// 返回事件消费状态
return result;
}
```
2、View分发的伪代码
```java
// 返回值代表是否将事件消费掉
public boolean dispatchTouchEvent(MotionEvent event) {
if(mOnTouchListener.onTouch(this, event)) {
return true;
} else if (onTouchEvent(event)) {
return true;
}
return false;
}
```
## 注意事项
* 拦截事件指事件不再往下传递,使用事件指从事件中获取想要的信息,消费事件指把事件吃了
* 只要给 View 注册了 onClickListener、onLongClickListener、OnContextClickListener 其中的任何一个监听器或者设置了 android:clickable=”true” 就代表这个 View 是可点击的,可点击的 View 就会消费事件
* ViewGroup 和 ChildView 同时注册了点击事件监听器时,事件优先给 ChildView 消费
* 同一次点击事件只能被一个 View 消费,防止事件混乱
# 绘制原理
1、LayoutInflater将布局中的xml标签转化为对象,CPU经过measure、layout、draw将他们转化为多边形(Polygons)或纹理(Texture),GPU对多边形或纹理进行栅格化操作,栅格化后的数据写入帧缓冲区中等待显示器显示。
2、CPU和GPU通过图形驱动层连接。
3、GPU负责绘制帧,屏幕负责逐行扫描显示帧,两者速度不一定保持一致。
4、因此引入垂直同步机制,由系统每隔16ms发出一次VSync信号后,CPU进行计算,GPU进行绘制,以此来强制GPU的刷新率和屏幕刷新率同步。
5、理想状态下屏幕每秒展示60帧时人眼感受不到卡顿,动画会比较流畅,因此为保持应用流畅,我们需要在16ms的时间内完成绘制,尽量减少measure、layout、draw的时间。有以下解决方案:
* 精简布局层级
* 使用\<include>标签重用布局,使用\<merge>标签合并布局
* 使用ViewStub仅在需要时加载
* 删除无用的控件和布局
* 使用性能较好的ViewGroup,如Constraintlayout等
* onDraw方法中不要创建局部对象,会占用过多内存导致频繁gc
* onDraw方法中不要执行耗时操作及循环操作
# 过度绘制
1、过度绘制指:屏幕上的某个像素在同一帧的时间内被绘制了多次。会浪费CPU和GPU资源。
2、开发者工具可提高可视化过度绘制情况:真彩色-没有过度绘制;蓝色-过度绘制1次;绿色-过度绘制2次;粉色-过度绘制3次;红色-过度绘制4次或更多
3、过度绘制解决方案:
* 去掉Window的默认背景(在onCreate方法中或者在theme中)
* 去掉视图中不必要的背景
* 使用ViewStub仅在需要时加载
* 使用\<include>标签重用布局,使布局更清晰明了;使用\<merge>标签合并布局,减少层级
* 采用性能更好的布局来精简层级
- 导读
- Java知识
- Java基本程序设计结构
- 【基础知识】Java基础
- 【源码分析】Okio
- 【源码分析】深入理解i++和++i
- 【专题分析】JVM与GC
- 【面试清单】Java基本程序设计结构
- 对象与类
- 【基础知识】对象与类
- 【专题分析】Java类加载过程
- 【面试清单】对象与类
- 泛型
- 【基础知识】泛型
- 【面试清单】泛型
- 集合
- 【基础知识】集合
- 【源码分析】SparseArray
- 【面试清单】集合
- 多线程
- 【基础知识】多线程
- 【源码分析】ThreadPoolExecutor源码分析
- 【专题分析】volatile关键字
- 【面试清单】多线程
- Java新特性
- 【专题分析】Lambda表达式
- 【专题分析】注解
- 【面试清单】Java新特性
- Effective Java笔记
- Android知识
- Activity
- 【基础知识】Activity
- 【专题分析】运行时权限
- 【专题分析】使用Intent打开三方应用
- 【源码分析】Activity的工作过程
- 【面试清单】Activity
- 架构组件
- 【专题分析】MVC、MVP与MVVM
- 【专题分析】数据绑定
- 【面试清单】架构组件
- 界面
- 【专题分析】自定义View
- 【专题分析】ImageView的ScaleType属性
- 【专题分析】ConstraintLayout 使用
- 【专题分析】搞懂点九图
- 【专题分析】Adapter
- 【源码分析】LayoutInflater
- 【源码分析】ViewStub
- 【源码分析】View三大流程
- 【源码分析】触摸事件分发机制
- 【源码分析】按键事件分发机制
- 【源码分析】Android窗口机制
- 【面试清单】界面
- 动画和过渡
- 【基础知识】动画和过渡
- 【面试清单】动画和过渡
- 图片和图形
- 【专题分析】图片加载
- 【面试清单】图片和图形
- 后台任务
- 应用数据和文件
- 基于网络的内容
- 多线程与多进程
- 【基础知识】多线程与多进程
- 【源码分析】Handler
- 【源码分析】AsyncTask
- 【专题分析】Service
- 【源码分析】Parcelable
- 【专题分析】Binder
- 【源码分析】Messenger
- 【面试清单】多线程与多进程
- 应用优化
- 【专题分析】布局优化
- 【专题分析】绘制优化
- 【专题分析】内存优化
- 【专题分析】启动优化
- 【专题分析】电池优化
- 【专题分析】包大小优化
- 【面试清单】应用优化
- Android新特性
- 【专题分析】状态栏、ActionBar和导航栏
- 【专题分析】应用图标、通知栏适配
- 【专题分析】Android新版本重要变更
- 【专题分析】唯一标识符的最佳做法
- 开源库源码分析
- 【源码分析】BaseRecyclerViewAdapterHelper
- 【源码分析】ButterKnife
- 【源码分析】Dagger2
- 【源码分析】EventBus3(一)
- 【源码分析】EventBus3(二)
- 【源码分析】Glide
- 【源码分析】OkHttp
- 【源码分析】Retrofit
- 其他知识
- Flutter
- 原生开发与跨平台开发
- 整体归纳
- 状态及状态管理
- 零碎知识点
- 添加Flutter到现有应用
- Git知识
- Git命令
- .gitignore文件
- 设计模式
- 创建型模式
- 结构型模式
- 行为型模式
- RxJava
- 基础
- Linux知识
- 环境变量
- Linux命令
- ADB命令
- 算法
- 常见数据结构及实现
- 数组
- 排序算法
- 链表
- 二叉树
- 栈和队列
- 算法时间复杂度
- 常见算法思想
- 其他技术
- 正则表达式
- 编码格式
- HTTP与HTTPS
- 【面试清单】其他知识
- 开发归纳
- Android零碎问题
- 其他零碎问题
- 开发思路