View的绘制流程学习总结

本文深入探讨了Android中View的绘制流程,包括Measure、Layout和Draw三个阶段。Measure阶段确定View的尺寸,Layout阶段确定View的位置,Draw阶段将View呈现在屏幕上。整个流程始于ViewRootImpl的performTraversals方法,通过measure()、layout()和draw()进行。MeasureSpec在测量阶段起关键作用,定义了三种测量模式:UNSPECIFIED、EXACTLY和AT_MOST。对于ViewGroup,measure流程会遍历并测量所有子View。布局类如RelativeLayout、LinearLayout等有自己的onMeasure实现,通过measureChild或measureChildWithMargins计算子View的MeasureSpec。最后,draw阶段通过dispatchDraw绘制子View,完成View的显示。
摘要由CSDN通过智能技术生成

文章目录


为什么需要学习View的绘制流程?Android提供了很多的基本View组件,如TextView、EditText、Button等等,这些组件在日常开发中经常要用到,也满足了大部分的需求。然而,有时候用户的要求很特殊,需要的效果基本组件根本实现不了,这时我们就要自定义View,于是View的绘制流程就派上用场了,不学习View的绘制流程,根本写不出符合需求的View,相反,学好了绘制流程,无论什么样的View你都可以实现,只有需求想不出,没有你写不出的自定义View。

简介

View的绘制流程可分为三大阶段,Measure(测量)、Layout(定位)、Draw(绘制),这三个阶段依次进行。Measure的本质就是确定组件的宽和高,Layout的本质是确定组件4个顶点的位置,Draw的本质就是把测量好的、定好位的组件画出来。为什么是这样的顺序?简单举个例子,画画的时候,下笔之前要先想好要画多大、画在哪里吧,画多大就是测量、画在哪就是定位,画这个动作就是绘制。

那么在是在哪调用这3个流程的呢?这里只需要知道入口是ViewRootImpl的performTraversals方法,再上面就是ActivityThread里的代码了,这里不需深究。performTraversals方法会依次调用performMeasure()、performLayout()、performDraw()来完成整个View树的绘制流程。看看其工作流程图。
在这里插入图片描述
注意这里并不是说performXXX()方法是ViewGroup里面的,它突出的重点是流程,比如View的measure阶段对应3个方法,preformMeasure->measure->onMeasure,在ViewGroup的onMeasure方法中又遍历子View调用它的measure流程,这样measure就从父View传到子View,子View重复父View的动作,直到反复遍历整个View树,measure阶段结束了就到layout阶段,以此类推。View经历完3个流程才会显示到界面上。

可以看看ViewRootImpl的部分源码,验证上面的内容。

private void performTraversals() {
   
	// 省略代码,通常是一些条件判断
	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
	// 省略代码,通常是一些条件判断
	performLayout(lp, mWidth, mHeight);
	// 省略代码,通常是一些条件判断
	performDraw();
}

performTraversals()方法的代码很长,全部贴出来不实际,可以自己在AndroidStudio看ViewRootImpl的源码进行验证。前提是你的AndroidStudio下载了源码文件。

下面分别详细讲解三大流程。

measure

要理解measure流程,就要先理解MeasureSpec,因为measure流程其实就是不断的计算MeasureSpec,并传递子View,子View再根据传来的MeasureSpec给某些变量赋值。

MeasureSpec
MeasureSpec是一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。可以把MeasureSpec简单理解为一个对象,里面有两个属性SpecMode和SpecSize,但为了节省空间,把它包装成一个32位的int值。

public static class MeasureSpec {
   
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;

	//可以理解为创建对象(传入两个属性size和mode)
	public static int makeMeasureSpec(int size,int mode) {
   
        if (sUseBrokenMakeMeasureSpec) {
   
            return size + mode;
        } else {
   
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
    // 下面对应两个get方法
    public static int getMode(int measureSpec) {
   
        //noinspection ResourceType
        return (measureSpec & MODE_MASK);
    }
    public static int getSize(int measureSpec) {
   
        return (measureSpec & ~MODE_MASK);
    }
}    

需要注意的是,MeasureSpec确实是一个类,但是measure流程使用的MeasureSpec是通过makeMeasureSpec返回的一个int值,并不是MeasureSpec类。

SpecMode

  • UNSPECIFIFD
    父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部(平时用的基础View和自定义View几乎没有这种情况)。
  • EXACTLY
    父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值。如下面所示。
android:layout_width="match_parent"
android:layout_width="100dp"
  • AT_MOST
    父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应与LayoutParams中的wrap_content。
android:layout_width="wrap_content"

注意:
这里并非说一个View在xml文件定义的属性是match_parent,它的SpecMode就是EXACTLY,还有视父View的SpecMode而决定的。所以,一个View的MeasureSpec是由父View的MeasureSpec和自身的LayoutParams属性所决定的。既然如此,那最顶层View的MeasureSpec是怎样得到的呢?下面进行分析。

上面提到measure流程的入口是performTraversals()方法,在里面依次调用了performXXX()函数开始三大流程,可能细心的人已经发现,performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);已经传入了MeasureSpec供后面的View使用。再看ViewRootImpl源码,查看childWidthMeasureSpec是怎么来的(childHeightMeasureSpec与其类似,一个View通常有两个MeasureSpec,分别代表宽和高)。

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 省略代码
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

可以看到这两个MeasureSpec是通过getRootMeasureSpec()方法得到的,看方法名就知道,这是获取根部MeasureSpec的方法,即顶层View的MeasureSpec。进入该方法查看。

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
   
        int measureSpec;
        switch (rootDimension) {
   

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值