学习笔记 | Android开发艺术之View(二)

一、View工作原理

1、知识储备
  • ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带。
  • View的绘制流程是从ViewRoot和performTraversals开始。
  • performTraversals()依次调用performMeasure()、performLayout()和performDraw()三个方法,分别完成顶级 View的绘制。其中,performMeasure()会调用measure(),measure()中又调用onMeasure(),实现对其所有子元素的measure过程,这样就完成了一次measure过程;接着子元素会重复父容器的measure过程,如此反复至完成整个View树的遍历。layout和draw同理。
  • measure用来测量View的宽和高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。
  • DecorView其实是一个FrameLayout,View层的事件都先经过DecorView,然后才传递给我们的View。
  • MeasureSpec
     a、作用:通过宽测量值widthMeasureSpec和高测量值heightMeasureSpec决定View的大小。

     b、构成:一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小)。

     c、三种测量模式:
       UNSPECIFIED :父视图不对子试图有任何的约束,表示子View想多大都可以。
       EXACTLY :父视图指定了确切的大小,无论子视图指定多大的尺寸,子视图必须在父视图指定的大小范围内,对应的属性为match_parent或者具体的值。
       AT_MOST :父控件为子控件指定一个最大尺寸,子视图必须确保自己的孩子视图可以适应在该尺寸范围内,对应的属性为wrap_content。

     d、MeasureSpec和LayoutParams的对应关系
        LayoutParams和父容器一起决定View的MeasureSpec,从而进一步决定View的宽/高
        对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。

     e、普通View的MeasureSpec的创建规则
       父容器的模式是精准模式,那么View也是精准模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。
2、View的工作流程

1、measure过程

  • View的measure过程
    View的measure过程由其measure方法来完成,measure方法是一个final类型的方法。在View的measure方法中会去调用View的onMeasure方法,其中setMeasuredDimension方法会通过getDefaultSize()设置View宽/高的测量值。
    getDefaultSize流程图如下:
    getDefaultSize流程图
public static int getDefaultSize(int size, int measureSpec) {  
//参数说明:
// 第一个参数size:提供的默认大小
// 第二个参数:宽/高的测量规格(含模式 & 测量大小)

    //设置默认大小
    int result = size; 

    //获取宽/高测量规格的模式 & 测量大小
    int specMode = MeasureSpec.getMode(measureSpec);  
    int specSize = MeasureSpec.getSize(measureSpec);  

    switch (specMode) {  
        // 模式为UNSPECIFIED时,使用提供的默认大小
        // 即第一个参数:size 
        case MeasureSpec.UNSPECIFIED:  
            result = size;  
            break;  
        // 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值
        // 即measureSpec中的specSize
        case MeasureSpec.AT_MOST:  
        case MeasureSpec.EXACTLY:  
            result = specSize;  
            break;  
    }  
 //返回View的宽/高值
    return result;  
}

    getDefaultSize返回的大小就是measureSpec中的specSize即View测量后的大小,这里多次提到测量后的大小,是因为View最终的大小是在layout阶段确定的

为什么在布局中使用wrap_content就相当于使用match_parent?
具体子View的测量模式和大小 其中的规律总结:(以子View为标准,横向观察)
在这里插入图片描述
(1)在onMeasure()中的getDefaultSize()的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。
(2)因为AT_MOST对应wrap_content;EXACTLY对应match_parent,所以,默认情况下,wrap_content和match_parent是具有相同的效果的。
(3)因为在计算子View MeasureSpec的getChildMeasureSpec()中,子View MeasureSpec在属性被设置为wrap_content或match_parent情况下,子View MeasureSpec的specSize被设置成parenSize = 父容器当前剩余空间大小。

所以:wrap_content起到了和match_parent相同的作用:等于父容器当前剩余空间大小。
解决方法:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小。

  • ViewGroup的measure过程
    对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法。ViewGroup是一个抽象类,所以没有重写View的onMeasure方法,而是提供了一个叫measureChildren的方法。
    measureChild的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure方法来进行测量。
    ViewGroup的measure过程如下:
    ViewGroup的measure过程为什么ViewGroup的measure过程不像单一View的measure过程那样对onMeasure()做统一的实现?
    因为不同的ViewGroup子类(LinearLayout、RelativeLayout / 自定义ViewGroup子类等)具备不同的布局特性,这导致他们子View的测量方法各有不同。

推荐阅读:自定义View Measure过程

2、layout过程

  • layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置。
  • 大致流程:首先会通过setFrame方法来设定View的四个顶点(mLeft、mRight、mTop和mBottom)的位置;接着会调用onLayout方法,用于父容器确定子元素的位置。View和ViewGroup均没有真正实现onLayout方法,因为它的具体实现同样和具体的布局有关。

View的getMeasuredWidth和getWidth这两个方法有什么区别?
在View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于View的measure过程,而最终宽/高形成于View的layout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。

3、draw过程

View的绘制过程:

  • 绘制背景background.draw(canvas)
  • 绘制自己(onDraw)
  • 绘制children(dispatchDraw)
  • 绘制装饰(onDrawScrollBars)

View绘制过程的传递是通过dispatchDraw来实现的,dispatchDraw会遍历调用所有子元素的draw方法,如此draw事件就一层层地传递了下去。

View.setWillNotDraw()

/**
  * 源码分析:setWillNotDraw()
  * 定义:View 中的特殊方法
  * 作用:设置 WILL_NOT_DRAW 标记位;
  * 注:
  *   a. 该标记位的作用是:当一个View不需要绘制内容时,系统进行相应优化
  *   b. 默认情况下:View 不启用该标记位(设置为false);ViewGroup 默认启用(设置为true)
  */ 

public void setWillNotDraw(boolean willNotDraw) {

    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);

}

应用场景
a. setWillNotDraw参数设置为true:当自定义View继承自 ViewGroup 、且本身并不具备任何绘制时,设置为 true 后,系统会进行相应的优化。

b. setWillNotDraw参数设置为false:当自定义View继承自 ViewGroup 、且需要绘制内容时,那么设置为 false,来关闭 WILL_NOT_DRAW 这个标记位。

推荐阅读:自定义View Draw过程

3、自定义View

具体介绍 & 使用场景自定义View的分类使用注意点
在这里插入图片描述
推荐阅读 :自定义View

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值