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


    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() 方法中的逻辑

    1. /frameworks/base/core/java/android/view/ViewRootImpl.java
    2. class ViewRootImpl {
    3. void performTraversals() {
    4. if(layoutRequested){//APP发起requestLayout请求
    5. boolean 视图尺寸发生变化 = measureHierarchy();//① 执行日常测量工作
    6. }
    7. if(首次添加视图/视图尺寸发生变化等){
    8. relayoutWindow();//Window大小确定下来以后,去sf申请对应大小的surface
    9. performMeasure();//② 申请到surface后再次测量,此方法结束后,该window的大小将会被确定,除非window的尺寸发生改变,否则不会再次执行该方法
    10. }
    11. }
    12. boolean measureHierarchy(){//执行测量,并返回和缓存的窗口大小比,新的测量尺寸是否发生变化
    13. performMeasure();
    14. }
    15. void performMeasure() {
    16. mView.measure();
    17. }
    18. }

    在创建 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 执行绘制流程

    1. /frameworks/base/core/java/android/view/ViewGroup.java
    2. class ViewGroup extends View {
    3. void draw(Canvas canvas) {
    4. drawBackground();//先画背景
    5. onDraw(canvas);//不管是View还是ViewGroup,先把自己画出来
    6. dispatchDraw(canvas);//通知子视图执行绘制,如果是视图是View,这个方法默认为空,只有ViewGroup有实现
    7. onDrawForeground(canvas);//最后画前景
    8. }
    9. void dispatchDraw(){
    10. for (int i = 0; i < childrenCount; i++) {
    11. draw();//简化过的路径
    12. }
    13. }
    14. }

    View 的绘制流程和 ViewGroup 的绘制流程几乎一模一样,唯一的区别是 View 中的 dispatchDraw() 是空实现,因为它没有子视图

    1. /frameworks/base/core/java/android/view/View.java
    2. class View {
    3. void draw(Canvas canvas) {
    4. drawBackground();//先画背景
    5. onDraw(canvas);//不管是View还是ViewGroup,先把自己画出来
    6. dispatchDraw(canvas);//空方法
    7. onDrawForeground(canvas);//最后画前景
    8. }
    9. void dispatchDraw();//空方法
    10. }

    Android 通常在 onDraw() 方法中会执行绘图操作,但当我们选择开启硬件加速之后,实际的绘制操作就不在 onDraw() 方法中执行了,接着往下看

    启用硬件加速

    总结一下,应用启用硬件加速以后,onDraw() 方法中的指令将不再被执行,而是被收集到 DisplayList 集合中,等到所有需要绘制的 View 的 draw() 方法执行结束后,这些指令将会被同步到 RenderThread 渲染线程执行真正的绘图工作

    ps:启用硬件加速和关闭硬件加速对于开发者来说是无感的,onDraw() 方法的收集工作依旧是在 UI 线程中执行,代码写的垃该卡还是会卡

  • 相关阅读:
    分布式文件系统FastDFS
    设计模式之桥接模式
    服务器端使用django websocket,客户端使用uniapp 请问服务端和客户端群组互发消息的代码怎么写的参考笔记
    中国计算机学会推荐国际学术会议和期刊目录
    怎么合并视频?快把这些方法收好
    HTML事件的种类
    [读论文]深度孵化 Deep Incubation: Training Large Models by Divide-and-Conquering
    Mysql 分布式序列算法
    vue源码解析
    这几个小妙招让你学会如何压缩图片大小
  • 原文地址:https://blog.csdn.net/cpcpcp123/article/details/127888671