Q:列举一个自定义view的例子,流式布局:
Android 自定义流式布局FlowLayout 自己造的轮子真香!_卤蛋还是茶叶蛋的博客-CSDN博客_android 自适应流布局,android实现支持适配器的flowlayout
Q:invalidate和requestLayout区别:
https://buder.blog.csdn.net/article/details/114761400
Q:硬件加速:
Android图形系统(四)应用篇:自定义View/ViewGroup详解 - 掘金
Q:MeasureSpec 声明了三种测量模式:
UNSPECIFIED :未指定模式,也可以称为无限制模式。当你收到此模式时,表明父视图不关心你的尺寸大小,你可以随意设置自己的尺寸信息。什么情况下可能收到 UNSPECIFIED 呢?比如当你的父视图是可以纵向滚动的 ScrollView ,那子视图的高度大小对于父视图来说没有意义。无论你多高(即使超出屏幕),都可以通过滑动屏幕来查看(同理,如果是横向滚动那么宽度就没有意义)EXACTLY :精确模式。当你收到此模式时,表示父视图希望你就这么大(不要小于或大于给定的大小),通常在 xml 中指定大小或者设为 match_parent 时会收到 EXACTLY 模式AT_MOST :最大模式。当你收到此模式时,表示你可以在父视图给定的范围内随意发挥,但最好不要超过父视图给你的大小,通常在 xml 中设为 wrap_content 时会收到 AT_MOST 模式Q:measure 发生多次的原因:
onMeasure 方法的回调次数,主要取决于它所在的容器的 onMeasure 逻辑,搭配不同 ViewGroup 和设置不同属性都会有影响。
比如 LinearLayout 在设置权重属性后就会多执行一次测量流程,在整个测量过程中,LinearLayout 最少会经历1次测量,最多会经历3次,所以由 ViewGroup 自身发起调用次数很难有一个标准、统一的答案
首次加载会有两次measure:
在首次加载 Activity 时,每个 View 最少也会执行2次 onMeasure() 方法,这2次调用都发生在 ViewRootImpl 类的 performTraversals() 方法中,其他的几次调用有的也还是发生在 ViewRootImpl 中,有的则是 ViewGroup 的个人行为。
View 必须依托 Surface 对象才能够显示绘制,所以,在 View 执行测量工作之前,Surface 的大小必须先确定下来
Surface 的大小什么时候确定的呢?我们来看 ViewRootImpl#performTraversals() 方法中的逻辑
- /frameworks/base/core/java/android/view/ViewRootImpl.java
- class ViewRootImpl {
-
- void performTraversals() {
- if(layoutRequested){//APP发起requestLayout请求
- boolean 视图尺寸发生变化 = measureHierarchy();//① 执行日常测量工作
- }
- if(首次添加视图/视图尺寸发生变化等){
- relayoutWindow();//Window大小确定下来以后,去sf申请对应大小的surface
- performMeasure();//② 申请到surface后再次测量,此方法结束后,该window的大小将会被确定,除非window的尺寸发生改变,否则不会再次执行该方法
- }
- }
-
- boolean measureHierarchy(){//执行测量,并返回和缓存的窗口大小比,新的测量尺寸是否发生变化
- performMeasure();
- }
-
- void performMeasure() {
- mView.measure();
- }
- }
在创建 Surface 的过程中,一共执行了两次测量(代码中标记为1/2号):
第一次是在
measureHierarchy()方法中调用了performMeasure()执行测量工作第二次是在
relayoutWindow()方法申请到 Surface 以后,再次调用performMeasure()发起的测量
这两次测量就是首次加载 Activity 时,View 都会执行2次 onMeasure() 方法的原因!
其他场景的多次measure:
Dialog 可能会引发多次调用,手动调用requestLayout可能发生1,3,9次measure
Q:measure、layout、draw的遍历顺序
一:测量和布局阶段都是深度优先遍历,先执行子 View 再执行 ViewGroup 自身;而绘制是先执行 ViewGroup 绘制流程,再执行子 View 的绘制流程
二:绘制流程涉及到其他硬件(GPU),启用硬件加速和关闭硬件加速方法走的是两条完全不同的路线
总结一下 View 在measure流程完成的事情:
setMeasuredDimension() 方法保存尺寸信息一个纵向的 LinearLayout 测量过程:
measureChildWithMargins() 方法为每个子 View 创建 MeasureSpec 并通知其执行 measuremTotalLength ,同时不断更新最大宽度的子 View,它的宽度就是 LinearLayout 将来的宽度setMeasuredDimension() 方法保存自身尺寸信息当 LinearLayout 的测量工作结束后,会开始执行它的父视图的测量流程,直到最顶部的 DecorView 的 measure 方法执行结束,到那时,整个视图所依附的 Window 的大小才可以确定下来
布局阶段的任务量主要是在 ViewGroup 一侧,子 View 不参与布局过程,父视图负责把子 View 们按照LayoutParams 规则摆放好。
layout() 事件的起点和测量事件一样,都在 ViewRootImpl 类中触发,布局流程执行的顺序也和测量流程相同,以 DecorView 作为 View 多叉树的根节点,深度优先遍历整棵树
先执行 ViewGroup 绘制流程,再执行子 View 的绘制流程
在 Android 系统中,先绘制的内容会被后绘制的内容覆盖掉,ViewGroup 会先执行自己的 onDraw() 方法执行绘图,之后才会执行第3步调用 dispatchDraw() 方法通知子 View 执行绘制流程
- /frameworks/base/core/java/android/view/ViewGroup.java
- class ViewGroup extends View {
- void draw(Canvas canvas) {
- drawBackground();//先画背景
- onDraw(canvas);//不管是View还是ViewGroup,先把自己画出来
- dispatchDraw(canvas);//通知子视图执行绘制,如果是视图是View,这个方法默认为空,只有ViewGroup有实现
- onDrawForeground(canvas);//最后画前景
- }
- void dispatchDraw(){
- for (int i = 0; i < childrenCount; i++) {
- draw();//简化过的路径
- }
- }
- }
View 的绘制流程和 ViewGroup 的绘制流程几乎一模一样,唯一的区别是 View 中的 dispatchDraw() 是空实现,因为它没有子视图
- /frameworks/base/core/java/android/view/View.java
- class View {
-
- void draw(Canvas canvas) {
- drawBackground();//先画背景
- onDraw(canvas);//不管是View还是ViewGroup,先把自己画出来
- dispatchDraw(canvas);//空方法
- onDrawForeground(canvas);//最后画前景
- }
- void dispatchDraw();//空方法
- }
Android 通常在 onDraw() 方法中会执行绘图操作,但当我们选择开启硬件加速之后,实际的绘制操作就不在 onDraw() 方法中执行了,接着往下看
总结一下,应用启用硬件加速以后,onDraw() 方法中的指令将不再被执行,而是被收集到 DisplayList 集合中,等到所有需要绘制的 View 的 draw() 方法执行结束后,这些指令将会被同步到 RenderThread 渲染线程执行真正的绘图工作
ps:启用硬件加速和关闭硬件加速对于开发者来说是无感的,
onDraw()方法的收集工作依旧是在 UI 线程中执行,代码写的垃该卡还是会卡