《建造我的Android技能塔之沙粒三:View工作流程》

本文详细探讨了Android中View的工作流程,包括自定义View的构造、测量、大小确定、布局和绘制等步骤。讲解了MeasureSpec的模式及其与LayoutParams的关系,强调了在wrap_content模式下如何正确设置自定义View的大小。同时,分析了ViewGroup的measure过程,指出LinearLayout等布局的测量细节,并提到了获取View宽高的最佳时机和方法。
摘要由CSDN通过智能技术生成

View工作流程


1.自定义View流程

开始 -> 构造函数 -> onMeasure() ->onSIzeChanged() ->onLaout() ->onDraw() -> 视图状态改变 -> 完成

  • 构造函数:View初始化
  • onMeasure:测量View大小
  • onSizeChanged:确定View大小
  • onLayout:确定子View布局
  • onDraw:实际绘制内容

2.MeasureSpec

MeasureSpec代表一个32位Int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode 是指测量模式,二SpecSize是指在某种测量模式下的规格大小。

MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作。其提供了打包和解包方法。SpecMode和SpecSize 也是一个int值,一组SpecMode和SpecSize 可以打包成一个MeasureSpec, 而一个MeasureSpec 可以通过解包获得 SpecMode和SpecSize 。这里提到的MeasureSpec是指所代表的int值,而非MeasureSpec本身。

SpecMode有三类:

  • UNSPECIFIED

    父容器不对VIew有任何限制,要多大有多大,这种情况一般用于系统内部,表示一种测量的状态。

  • EXACTLY

    父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。他对应于LayoutParams中的match_parent 和具体数值这两种模式

  • AT_MOST

    父容器指定了一个可用大小即SpecSize,VIew的大小不能大于这个值,具体是什么值,要看不同View的具体实现。它对应于LayoutParams 中的 wrap_content。

3.MeasureSpec 和 LayoutParams 的对印关系

系统内部是通过MeasureSpec来进项View的测量,但是正常情况下,我们使用View指定MeasureSpec,尽管如此,但是我们可以给View 设置LayoutParams。在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。需要注意的是,MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽高。另外,对于顶级View(DecorView)和普通VIew来说,MeasureSpec 的转换过程略有不同。对于DecorView,其MeasureSpec 由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec 由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasureSpec中就可以确定VIew的测量宽高。

4.View的Measure过程

在View的measure方法中回去调用View的onMeasure方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

setMeasuredDimension方法设置View的宽/高的测量值,因此我们只需要看getDefaultSize这个方法。

    public static int getDefaultSize(int size, int measureSpec) {
   
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
   
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

getDefaultSize 返回的result 就是 MeasureSpec 中的 SpecSize,而这个SpecSize就是View测量后的大小,这里多次提到的测量后的大小,是因为View的最终大小实在layout阶段确定的,所以必须要加以区分,,但是几乎所有情况下View的测量大小和最终大小是相等的。

至于UNSPECIFIED这种情况,一般用于系统内部的测量过程,在这种情况下,View的大小为getDefaultSize的第一个参数size,即宽高分别为 getSuggestedMinimumWidth 、getSuggestedMinimumHeight 这两个方法的返回值。

protected int getSuggestedMinimumWidth() {
   
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

protected int getSuggestedMinimumHeight() {
   
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    }

从上面可以看出,如果View没有设置背景,那么view的宽度为mMinWidth ,而 mMinWidth 对应于 android:minWidth 这个属性所对应的值,所以 没有设置背景,View的宽度即为 android:minWidth 所指定的值。如果这个属性如果不指定,那么MMinWidth 则 默认为0;如果View指定了背景,则View的宽度为 max(mMinWidth, mBackground.getMinimumWidth());

public int getMinimumWidth() {
   
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}


/**
     * Returns the drawable's intrinsic width.  返回可绘制对象的固有宽度
     * <p>
     * Intrinsic width is the width at which the drawable would like to be laid
     * out, including any inherent padding. If the drawable has no intrinsic
     * width, such as a solid color, this method returns -1.
     *
     * @return the intrinsic width, or -1 if no intrinsic width
     */
    public int getIntrinsicWidth() {
   
        return -1;
    }

源码中可以看出,getMinimumWidth返回的就是Drawable的原始宽度,前提是这个Drawable有原始宽度,否则就返回0; 那么Drawable 在什么情况下有原始宽度呢? ShapeDrawable 无原始宽高,而BitmapDrawable有原始宽高。(ps: 我还是不懂 o(╥﹏╥)o)

getMinimumWidth:如果View没有设置背景,那么返回android:minWidth所指定的值,可以为0;如果View设置了背景,则返回 android:minWidth 和 背景的最小宽度 这两者中的最大值,最终返回值就是View在UNSPECIFIED情况下 测量的宽高。

EXACTLY AT_MOST UNSPECIFIED
dp/px childSize childSize childSize
match_parent parentSize parentSize 0
warp_content parentSize parentSize 0

从getDefaultSize方法的实现来看,View的宽高有SpecSize决定,所以我们可以得出如下结论:直接继承View的自定义控件需要重写onMeasure方法并设定wrap_content时自身的大小,否则在布局中使用wrap_content就相当于使用match_parent。为什么呢? 从上述代码中我们知道,若果VIew在布局中使用wrap——content,那么它的specMode 是AT_MOST模式,在这种模式下,它的宽高就等于SpecSize;由上表可知,这种情况下View的SpecSize是parentSize,而parentSize是父容器中目前可以使用的大小,也就是父容器当前剩余的空间大小。很显然, view的宽高就等于父容器当前剩余空间的大小,这种效果和在布局中使用match_parent完全一致。

那么,如何 解决这个问题呢?

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   
        super.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值