面试:自定义view / viewgroup 相关问题
admin
2024-01-25 04:54:14
0

Q:列举一个自定义view的例子,流式布局:

自定义View 实现流式布局FlowLayout - 简书

Android 自定义流式布局FlowLayout 自己造的轮子真香!_卤蛋还是茶叶蛋的博客-CSDN博客_android 自适应流布局,android实现支持适配器的flowlayout

Q:invalidate和requestLayout区别:

https://buder.blog.csdn.net/article/details/114761400

Q:硬件加速:

面试:硬件加速相关_沙漠一只雕得儿得儿的博客-CSDN博客

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申请对应大小的surfaceperformMeasure();//② 申请到surface后再次测量,此方法结束后,该window的大小将会被确定,除非window的尺寸发生改变,否则不会再次执行该方法}}boolean measureHierarchy(){//执行测量,并返回和缓存的窗口大小比,新的测量尺寸是否发生变化performMeasure();}void performMeasure() {mView.measure();}
}

在创建 Surface 的过程中,一共执行了两次测量(代码中标记为1/2号):

  1. 第一次是在 measureHierarchy() 方法中调用了 performMeasure() 执行测量工作

  2. 第二次是在 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过程:

总结一下 View 在measure流程完成的事情:

  1. 根据父视图传递的 MeasureSpec,合理的计算自己所需的尺寸大小
  2. 在确定了自身尺寸后,调用 setMeasuredDimension() 方法保存尺寸信息

二、viewgroup的measure过程:

一个纵向的 LinearLayout 测量过程:

  • 调用了 measureChildWithMargins() 方法为每个子 View 创建 MeasureSpec 并通知其执行 measure
  • 子 View 测量完成后,获取每个子 View 的高度累加到成员变量 mTotalLength ,同时不断更新最大宽度的子 View,它的宽度就是 LinearLayout 将来的宽度
  • 循环结束表示所有子 View 全都测量完成,调用 setMeasuredDimension() 方法保存自身尺寸信息

当 LinearLayout 的测量工作结束后,会开始执行它的父视图的测量流程,直到最顶部的 DecorView 的 measure 方法执行结束,到那时,整个视图所依附的 Window 的大小才可以确定下来

三、view / viewgroup 的布局layout

布局阶段的任务量主要是在 ViewGroup 一侧,子 View 不参与布局过程,父视图负责把子 View 们按照LayoutParams 规则摆放好。

layout() 事件的起点和测量事件一样,都在 ViewRootImpl 类中触发,布局流程执行的顺序也和测量流程相同,以 DecorView 作为 View 多叉树的根节点,深度优先遍历整棵树

四、view / viewgroup 的 draw绘制阶段

先执行 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 线程中执行,代码写的垃该卡还是会卡

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...