面试一问:关于 View测量、布局及绘制原理

前言

2020年2月22.距离新年已经过去了大半个月了,依旧的出不了门,依旧的躲在家里一日三餐,依旧的在家办公,也不知道下周会不会复工,再次汇总手中各种保存整理的笔记

相关内容后续GitHub更新,想冲击金三银四的小伙伴可以找找看看,欢迎star
顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)

一、View绘制的流程框架


View的绘制是从上往下一层层迭代下来的。DecorView-->ViewGroup(--- >ViewGroup)-->View ,按照这个流程从上往下,依次measure(测量),layout(布 局),draw(绘制)

二、Measure流程

顾名思义,就是测量每个控件的大小。

调用measure()方法,进行一些逻辑处理,然后调用onMeasure()方法,在其中调用 setMeasuredDimension()设定View的宽高信息,完成View的测量操作。

  public final void measure(int widthMeasureSpec, int heightMeasur eSpec) {
  }

measure()方法中,传入了两个参数 widthMeasureSpec, heightMeasureSpec 表示 View的宽高的一些信息。

  protected void onMeasure(int widthMeasureSpec, int heightMeasure Spec) {
     setMeasuredDimension(getDefaultSize(getSuggestedMinimumW idth(), widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(), heig htMeasureSpec));
   }

由上述流程来看Measure流程很简单,关键点是在于widthMeasureSpec, heightMeasureSpec这两个参数信息怎么获得?

如果有了widthMeasureSpec, heightMeasureSpec,通过一定的处理(可以重写,自 定义处理步骤),从中获取View的宽/高,调用setMeasuredDimension()方法,指定 View的宽高,完成测量工作。

MeasureSpec的确定

先介绍下什么是MeasureSpec

MeasureSpec由两部分组成,一部分是测量模式,另一部分是测量的尺寸大
小。 其中,Mode模式共分为三类

UNSPECIFIED 不对View进行任何限制,要多大给多大,一般用于系统内部

EXACTLY 对应LayoutParams中的match_parent和具体数值这两种模式。检测到 View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值,

AT_MOST 对应LayoutParams中的wrap_content。View的大小不能大于父容器 的大小。

那么MeasureSpec又是如何确定的?

对于DecorView,其确定是通过屏幕的大小,和自身的布局参数LayoutParams

这部分很简单,根据LayoutParams的布局格式(match_parentwrap_content或 指定大小),将自身大小,和屏幕大小相比,设置一个不超过屏幕大小的宽高,以 及对应模式。 对于其他View(包括ViewGroup),其确定是通过父布局的MeasureSpec和自身的 布局参数LayoutParams。 这部分比较复杂。以下列图表表示不同的情况:

当子View的LayoutParams的布局格式是wrap_content,可以看到子View的大小 是父View的剩余尺寸,和设置成match_parent时,子View的大小没有区别。为了 显示区别,一般在自定义View时,需要重写onMeasure方法,处理wrap_content 时的情况,进行特别指定。

从这里看出MeasureSpec的指定也是从顶层布局开始一层层往下去,父布局影响 子布局。

可能关于MeasureSpec如何确定View大小还有些模糊,篇幅有限,没详细具体展开介绍

View的测量流程:

三、Layout流程

测量完View大小后,就需要将View布局在Window中,View的布局主要通过确定上 下左右四个点来确定的。

其中布局也是自上而下,不同的是ViewGroup先在layout()中确定自己的布局,然 后在onLayout()方法中再调用子View的layout()方法,让子View布局。在Measure 过程中,ViewGroup一般是先测量子View的大小,然后再确定自身的大小。

  public void layout(int l, int t, int r, int b) {
    // 当前视图的四个顶点
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight; 

    // setFrame() / setOpticalFrame():确定View自身的位置
   // 即初始化四个顶点的值,然后判断当前View大小和位置是否发生了变化并返回 

  boolean changed = isLayoutModeOptical(mParent) ?
             setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
   //如果视图的大小和位置发生变化,会调用onLayout()
   if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PF LAG_LAYOUT_REQUIRED) {
            // onLayout():确定该View所有的子View在父容器的位置
           onLayout(changed, l, t, r, b);
    ...
  }

上面看出通过 setFrame() / setOpticalFrame():确定View自身的位置,通过 onLayout()确定子View的布局。 setOpticalFrame()内部也是调用了 setFrame(),所以具体看setFrame()怎么确定自身的位置布局。

  protected boolean setFrame(int left, int top, int right, int bot tom) {
     ...
  // 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点
  // 即确定了视图的位置
       mLeft = left;
       mTop = top;
       mRight = right;
       mBottom = bottom; 

       mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBott om);
  }

确定了自身的位置后,就要通过onLayout()确定子View的布局。onLayout()是一个 可继承的空方法。

  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  }

如果当前View就是一个单一的View,那么没有子View,就不需要实现该方法。

如果当前View是一个ViewGroup,就需要实现onLayout方法,该方法的实现个自 定义ViewGroup时其特性有关,必须自己实现。

由此便完成了一层层的的布局工作。 View的布局流程:

四、Draw过程

View的绘制过程遵循如下几步:

①绘制背景 background.draw(canvas)
②绘制自己(onDraw
③绘制Children(dispatchDraw)
④绘制装饰(onDrawScrollBars

从源码中可以清楚地看出绘制的顺序。

  public void draw(Canvas canvas) {
  // 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写 此方法)
  // 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘 制。
  // 如果自定义的视图确实要复写该方法,那么需要先调用 super.draw(canvas)完 成系统的绘制,然后再进行自定义的绘制。
    ...
    int saveCount;
    if (!dirtyOpaque) {
       // 步骤1: 绘制本身View背景
      drawBackground(canvas);
    } 

      // 如果有必要,就保存图层(还有一个复原图层)
      // 优化技巧:
      // 当不需要绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
      // 因此在绘制的时候,节省 layer 可以提高绘制效率
      final int viewFlags = mViewFlags;
      if (!verticalEdges && !horizontalEdges) { 

      if (!dirtyOpaque)
          // 步骤2:绘制本身View内容 默认为空实现, 自定义View时需 要进行复写
          onDraw(canvas);
      ......
      // 步骤3:绘制子View 默认为空实现 单一View中不需要实现,ViewG roup中已经实现该方法
      dispatchDraw(canvas);
      ........
      // 步骤4:绘制滑动条和前景色等等
      onDrawScrollBars(canvas);
      ..........
      return; 

    }
     ...
  }

无论是ViewGroup还是单一的View,都需要实现这套流程,不同的是,在 ViewGroup中,实现了 dispatchDraw()方法,而在单一子View中不需要实现该方 法。自定义View一般要重写onDraw()方法,在其中绘制不同的样式。

View绘制流程:

五、总结

从View的测量、布局和绘制原理来看,要实现自定义View,根据自定义View的种 类不同,可能分别要自定义实现不同的方法。但是这些方法不外乎:
onMeasure() 方法,onLayout()方法,onDraw()方法。

onMeasure()方法: 单一View,一般重写此方法,针对wrap_content情况,规定 View默认的大小值,避免于match_parent情况一致。ViewGroup,若不重写,就会 执行和单子View中相同逻辑,不会测量子View。一般会重写onMeasure()方法,循 环测量子View。

onLayout()方法: 单一View,不需要实现该方法。ViewGroup必须实现,该方法是 个抽象方法,实现该方法,来对子View进行布局。

onDraw()方法: 无论单一View,或者ViewGroup都需要实现该方法,因其是个空 方法
自己整理的983页面试大全,为打算面试或者正在面试的人提供借鉴的思路

上图知识汇总的PDF相关内容后续GitHub更新,想冲击金三银四的小伙伴可以找找看看,欢迎star
顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)

原文地址:https://blog.51cto.com/14541311/2472974

时间: 2024-11-29 15:55:59

面试一问:关于 View测量、布局及绘制原理的相关文章

[译]Android view 测量布局和绘制的流程

原文链接 创造优秀的用户体验是我们开发者的主要目标之一.为此, 我们首先要了解系统是如何工作的, 这样我们才可以更好的与系统配合, 从它的优点中获益, 规避它的缺陷. 之前关于Android渲染过程的文章 这次我们主要关注Measure/Layout(测量和布局)的阶段, 这些阶段决定了视图的大小和位置, 以便于我们能够绘制它. Step 1: Measure 测量 目标: 确定是图的大小 视图的大小包含其子视图的大小, 且必须符合其父视图的要求 视图的大小由2个方面决定: 测量宽度与测量高度

[05] Android View 测量-布局-绘制流程

Android GUI之View测量

在上篇文章(http://www.cnblogs.com/jerehedu/p/4607599.html#gui)中,根据源码探索了View的绘制过程,过程有三个主要步骤,分别为测量.布局.绘制.系统对绘制已经做了很好的封装,我们主要对测量和布局过程进行分析,看一看android是如何对view进行测量和布局的. 根据上篇文章的分析,我们知道在ViewRootImpl的performMeasure方法中,实际上调用了mView.measure(childWidthMeasureSpec, chi

Android View 测量流程(Measure)完全解析

前言 上一篇文章,笔者主要讲述了DecorView以及ViewRootImpl相关的作用,这里回顾一下上一章所说的内容:DecorView是视图的顶级View,我们添加的布局文件是它的一个子布局,而ViewRootImpl则负责渲染视图,它调用了一个performTraveals方法使得ViewTree开始三大工作流程,然后使得View展现在我们面前.本篇文章主要内容是:详细讲述View的测量(Measure)流程,主要以源码的形式呈现,源码均取自Android API 21. 从ViewRoo

面试被问懵?带你一步一步深入Handler源码,不信还拿不下面试官?

Handler机制是Android中相当经典的异步消息机制,在Android发展的历史长河中扮演着很重要的角色,无论是我们直接面对的应用层还是FrameWork层,使用的场景还是相当的多. 很多朋友面试时问到了这里,一时被问懵.从哪里跌倒就从哪里爬起来,带大家一步一步深入Handler源码,就不信还拿不下面试官! BATJ.字节跳动面试专题,算法专题,高端技术专题,混合开发专题,java面试专题,Android,Java小知识,到性能优化.线程.View.OpenCV.NDK等已经上传到了的我的

Collection View 自定义布局(custom flow layout)

Collection view自定义布局 一般我们自定义布局都会新建一个类,继承自UICollectionViewFlowLayout,然后重写几个方法: prepareLayout():当准备开始布局时调用这个方法,可以在这里计算一些属性,比如cell的尺寸. layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?:在这里返回布局属性. 实例(比较简单的例子,实际开发中可以进行

面试常问问题:银行网上支付项目中怎么控制多线程高并发访问?

面试常问问题:银行网上支付项目中怎么控制多线程高并发访问? synchronized关键字主要解决多线程共享数据同步问题. ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题. ThreadLocal和Synchonized都用于解决多线程并发访问.但是ThreadLocal与synchronized有本质的区别: synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问.而ThreadLocal为每一个线程都提供了变量的副本,使 得每个线程在某一时

js原生方法的使用(面试必问)

废话不说,直接上题. slice(),接收两个参数,第一个为开始index(从0开始),第二个为结束的index(也是从0开始,但是不包括index本身,只到index-1).返回值是截取的数组,原数组不变化.传第三个参数,没有作用. splice(),接收无数个参数,第一个为开始的index(从0开始),第二个为删除的元素的个数(0代表不删除,1代表删除一个...),第三个为添加的元素,第四个也为添加的元素.....,返回值是删除的元素组成的数组,如果删除了0个就返回空数组,原数组会被改变成被

java面试常问的几个问题

1,作用域public,protected,private,以及不写时的区别 2,ArrayList和Vector的区别,HashMap和Hashtable的区别 3,char型变量能不能定义为一个中文?为什么? 4,多线程有几种表示方法,都是什么?同步有几种实现方法,都是什么? 5,继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么? 6,内部类的实现方式? 7,垃圾回收机制,如何优化程序? 8,float型float f=3.4是否正确? Jsp方面 1,jsp有哪些内置对象?作用