深入认识Android中View工作原理之View绘制

本文深入探讨了Android中View的工作原理,重点在于onMeasure(), onLayout()和onDraw()这三个关键方法。onMeasure用于测量View的大小,onLayout确定View的位置,onDraw则负责绘制内容。 MeasureSpec的specMode有EXACTLY, AT_MOST, UNSPECIFIED三种模式。View的绘制从ViewRoot的performTraversals()开始,经过DecorView,递归完成View树的绘制过程。" 113194936,10536603,安装LogAnalyzer:syslog日志管理工具,"['syslog工具', '日志分析', '数据库管理', 'LAMP环境', '网络监控']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上篇文章认识了下View从XML文件-->View对象的流程,里面用到了递归的方式来不断查找Layout文件中的ViewTag,利用反射方式,生成相对应View对象,添加到parent中,最后添加到DecorView,也就是我们的根布局里。好,我们现在拿到了View实例,接下来还要处理从View实例传递到手机屏幕上显示,实时刷新,保证60Hz(一帧约为16ms),怎么可以更快的绘制,双缓冲,三缓冲策略等等非常复杂的问题。Android在framework曾已经搭建好这一整套的流程框架,我们大多数情况下,只需要重写三个方法:onMeasure(),onLayout(),onDraw()就可以实现我们自己的业务需求。

下面就是对测量(onMeasure),布局(onLayout),绘制(onDraw)详细认识下。

onMeasure

先了解下整体流程:

onMeasure()方法顾名思义就是用于测量视图的大小的。View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。简单来说,ViewGroup中负责调用所有子View的测量方法,子View中负责实际的测量并设值。在子View中若发现有ViewGroup类型,再调用所有子View的测量方法,周而复始,直到当前View没有包含

measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。MeasureSpec这个取值非常巧妙,首先它是int类型,共32bit,前2位代表Mode,后30位代表Size。specMode一共有三种类型,如下所示:

1. EXACTLY

表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

2.AT_MOST

表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

3.UNSPECIFIED

表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

widthMeasureSpec和heightMeasureSpec这两个值又是从哪里得到的呢?通常情况下,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?这就需要去分析ViewRoot中的源码了,观察performTraversals()方法可以发现如下代码:

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

可以看到,这里调用了getRootMeasureSpec()方法去获取widthMeasureSpec和heightMeasureSpec的值,注意方法中传入的参数,其中lp.width和lp.height在创建ViewGroup实例的时候就被赋值了,它们都等于MATCH_PARENT。然后看下getRootMeasureSpec()方法中的代码,如下所示:

private int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    case ViewGroup.LayoutParams.MATCH_PARENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

可以看到,这里使用了MeasureSpec.makeMeasureSpec()方法来组装一个MeasureSpec,当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的。

接下来我们看下View的measure()方法里面的代码吧,onMeasure()就在这个方法里调用,如下所示:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMe
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值