热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

面试:自定义view/viewgroup相关问题

列举一个自定义view的例子,流式布局:自定义View实现流式布局FlowLayout-简书Android自定义流式布局FlowLayout自己造的

列举一个自定义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 }

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



推荐阅读
author-avatar
xiaodubo1R
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有