View 的绘制过程

声明

配合Activity 从启动到布局绘制的简单分析 阅读java

View的绘制.png

更多精品文章分类canvas

基本概念介绍

  • Activity:一个 Activity 是一个应用程序组件,提供一个屏幕,用户能够用来交互。
  • View:全部视图控件的基类
  • ViewGroup:View 的子类,是容器类控件,内部用于放置子View
  • Window:概况了 Android 窗口的基本属性和基本功能(抽象类)
  • PhoneWindow:Window 的实现类
  • DecorView: 界面的 根 View,PhoneWindow 的内部类,FrameLayout 的子类
  • ViewRootImpl:官方定义是 The top of a view hierarchy,implementing the needed protocol between View and the WindowManager. 在 View 层级中的顶层,能够认为是 View 树的根(注意 ViewRootImpl 不是 View,只是根,DecorView 是根 View,属于 View)用于串联 Window 和 View视图
  • WindowManager:是用来管理窗口的(Window)它的实现对象是 WindowManagerImpl,内部的大部分方法真正的实现是 WindowMangerGlobal
  • WindowManagerService:简称 WMS,做用是管理全部应用程序中的窗口


Android页面来自网络.png

Activity 启动过程简单介绍

Activity 设置页面布局的过程网络

  1. 在 ActivityThread 主线程中 newActivity 生成一个 Activity函数

  2. 而后调用 Activity 的attach 方法,attach 方法中生成 PhoneWindow 对象布局

  3. setContentView 中初始化 DecorView (ViewGroup 的子类)其实真正的执行是在 PhoneWindow 中的 setContentViewspa

  4. 在 LayoutInflater 中对布局文件进行 xml 解析获取对象的数据.net

  5. 根据解析出的数据执行 View 的构造函数进行 View 的建立。线程

    上面内容是在 onCreate() 中执行完成的3d

  6. 而后在 onResume 执行完成后调用View的绘制code

详细的说明看:Activity 从启动到布局绘制的简单分析

View 的绘制

View 的绘制流程能够分红三步:测量、布局、绘制

分别对应了:onMeasure() onLayout() onDraw 固然这个过程当中也会调用许多其余的方法,都是做为辅助,大的流程就这三步。其中这三步内部的执行都是呈现树状结构,从根 View 开始循环递进,直到全部子 View 所有执行完毕。

测量 onMeasure

onMeasure(int widthMeasureSpec,int heightMeasureSpec) 这个方法对于单控件来讲,只是测量他本身,可是对于 ViewGroup 来讲还要正确的给它的子控件传入指望的测量数值。而后根据全部子控件的大小和 onMeasure 中的参数来设置本身自己的大小。

关于 MeasureSpec 就很少解释了,这里只说一下内部的三种模式

  • MeasureSpec.EXACTLY 意思是精确大小,当你想给这个 View 一个精确的大小的时候就是用这个参数,好比布局中指定了大小是 10 dp 或者 match_parent 都是属于这种类型的
  • MeasureSpec.AT_MOST 意思是父布局会给一个最大的值,大小不能超过这个值(就是定义的这种标准,固然你不按照这个标准,在本身写 onMeasure() 的时候,明明父布局给的类型是 AT_MOST 你还要超过,那也没事,只是布局的样子可能就会有问题了)。对应 wrap_content 模式
  • MeasureSpec.UNSPECIFIED 意思没有指定尺寸,这种状况不常见,通常都是父控件是 AdapterView 经过 measure 方法传入模式。

关于 ViewGroup 的自定义,onMeasure() 方法内部须要实现什么?

首先咱们须要给这个控件设置正确的指望大小 setMeasuredDimension(width,height) 要想正确的获取 width 和 height 还须要根据 onMeasure(int widthMeasureSpec,int heightMeasureSpec) 中的参数来肯定。若是给的参数类型是 EXACTLY 的话,说明它的父控件给他的大小是肯定的,这个时候的大小就填写参数中的数值大小就好(须要 MeasureSpec.getSize(int))。若是参数类型是 AT_MOST 的时候,这个表示父布局给了一个值,当前的 View 的大小不能超过这个值。那么咱们就须要本身计算出这个 View 想要的大小,而后和父布局给的最大的值作比较,选择一个值给 setMeasuredDimension()

那么如何获取此 ViewGroup 的正确高度呢?作法就是要获取到每一个子View 的高度和一些 padding Margin 加起来就是这个 ViewGroup 应该的高度了。

要想获取子 View 的高度就须要调用 child.measure() 而后 child.getMeasureHeight 就获取 Child 的高度了。也就是说须要咱们给子 View 测量一下,测量的时候咱们须要传入值。固然这个值也不是随便传入的,若是你随便传入的话,那么 child 的大小就乱了,和你在布局文件中设定的大小就不同了。

那么若是正确的给 child 传入值呢?LinearLayout 是这样作的,固然咱们能够根据咱们想要的布局来进行自定义。

// 核心代码

// count 是 child 的个数
for(int i=0;i<count;i++){
    // 获取 child 的 LayoutParmas 这个对象有咱们在 xml 中给 view 设置的大小信息
	final LayoutParams lp = (LayoutParams)child.getLayoutParams();
    // 而后根据 LayoutParams 中的参数和 ViewGroup自己的 widthMeasureSpec 来进行对比,选择一个合适的数值给
    // child LinearLayout 具体的作法是经过 
    // ViewGroup 中的 getChildMeasureSpec 方法来获取一个合适值
    
}


// ViewGroup 中的 getChildMeasureSpec(int spec,int padding,int childDimension) 方法的实现代码

// spec 是 onMeasure 中的 spec padding 是子View 的margin + 父控件的 padding childDimension 是子 View 在布局文件中给定的大小
public static int getChildMeasureSpec(int spec,int padding,int childDimension){
    int specMode = MeasureSpec.getMode(spec);
    int SpecSize = MeasureSpec.getSize(spec);
    // 得出 ViewGroup 实际可使用的大小
    int size = Math.max(0,specSize-padding);
    
    int resultSize = 0;
    int resultMode = 0;
    // 而后就是根据 specMode 和 childDimension 来得出合适的大小。
}

复制代码

布局 onLayout

onLayout 对于子控件来讲没有什么意义,对于 ViewGroup 来讲,onLayout 方法内部要对子控件进行布局,调用子控件的 layout 函数。

onLayout 重写的时候,只须要获取子 View 的实例,而后调用子 View 的 layout 方法来实现布局就能够了,具体 layout 中传入的参数,是重写 onLayout 的重点。须要经过 getMeasureHeight 等获取子 View 的理想高度,而后再根据具体状况传入数值。

绘制 onDraw

onDraw() 函数就是来绘制了,通常 ViewGroup 不会实现内部的方法,子控件才重写 onDraw() 方法。也是内部一层层分发绘制。呈现树状结构

// 最根部调用下面的方法
// public void draw(Canvas canvas);
// 而后此方法内部调用 onDraw()(针对于 子View的)dispatchDraw(Canvas canvas) (主要是针对于 ViewGroup 的)
// 而后 dispatchDraw() 内部会调用 drawChild(Canvas canvas,View child,long drawingTime) 而后此方法内部会执行 draw 方法,就这样一层一层下去了。若是最终到了子View就会终止,由于子View dispatchDraw 方法体是空的。

//
复制代码

另外能够认为这三个方法都对应着 measure()layout() draw() 方法。能够认为这三个方法内部调用了上面的方法。

上面 onMeaure onLayout onDraw() 都介绍完了,那么最根处的 View 是怎么调用的呢?

布局树.png

能够看到上面这张图,追溯到根View DecorView ,其实最开始就是 ViewRootImp 来调用 DecorView 的 measure() ,而且传入了具体的值,这个值通常就是页面的大小。而后在 DecorView 的 measure 方法内部会调用 onMeasureonMeasure 的内部又会调用它的子 View 的 measure 而后就这样一层层的下去了,直到全部子 View 执行完毕,DecorView 的 measure 就执行完毕了,到此整个页面的测量工做完成。

onLayout 也是最早 ViewRootImp 来调用 DecorView 的 layout() 开始。onDraw 也是最早 ViewRootImp 来调用 DecorView 的 draw() 开始的。而后 draw() 的内部的执行就和上面介绍 onDraw() 中同样了

到此整个页面的测量、布局、绘制就所有分析完毕了。

能够查看:Activity 从启动到布局绘制的简单分析

相关文章
相关标签/搜索