View的工作原理

news2024/12/23 13:19:28

View的工作原理

当Activity对象被创建的时候,会将DecorView添加到Window中,同时创建ViewRootImpl对象并将它和DecorView关联起来

ViewRoot对应于ViewRootImpl类,它是连接WindowManagerDecorView的纽带,View的三大流程均是通过ViewRoot完成的

View的绘制是从ViewRootperformTraversals方法开始

MeasureSpec

在测量过程中,系统会将View中的LayoutParams根据父容器所施加的规则转化为对应的MeasureSpec,然后根据MeasureSpec来测量出View的最终宽与高

记住,这是测量,它不一定为View的最终的宽与高

MeasureSpec是一个32位int值,高2位代表SpecMode,剩下的代表SpecSize

1.SpecMode

SpecMode分为3种,

  1. UNSPECIFIED,是父容器对子View没有限制,要多大给多大
  2. EXACTLY,是父容器检测出子View的实际大小,View的最终大小就是SpecSize所指定的值
  3. AT_MOST,View的值不能超过这个值

2.MeasureSpec与LayoutParams的关系

在View的测量过程中,系统会将LayoutParams父容器的约束下转化为对应的MeasureSpec,然后根据MeasureSpec来确定view测量后的宽/高

但是MeasureSpec并不只是由LayoutParams一个决定。它也由父容器决定

View种类父容器LayoutParams
顶级View(DecorView)窗口尺寸自身的LayoutParams
普通View父容器的MeasureSpec自身的LayoutParams

一旦MeasureSpec确定后,onMeasure可以确定View的测量宽/高


3.获得DecorView的MeasureSpec相关源码

3.1ViewRootImpl的measureHierarchy

在ViewRootImpl中measureHierarchy方法中

childwidthMeasureSpec = getRootMeasureSpec(desiredwindowwidth,lp.width); 
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight,lp.height); performMeasure(childwidthMeasureSpec,childHeightMeasureSpec);

展示了DecorView的MeasureSpec的创建过程,把屏幕的宽度,屏幕的高度分别传给childwidthMeasureSpecchildHeightMeasureSpec

然后,performMeasure()方法被调用该方法会触发子视图的测量过程,包括调用子视图的onMeasure()方法。

点击getRootMeasureSpec看它怎么根据期望的窗口宽度和子视图的布局参数计算子视图的宽度测量规格

根据期望的窗口高度和子视图的布局参数lp.height)计算子视图的高度测量规格childHeightMeasureSpec)。

3.2getRootMeasureSpec

先看看源码

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.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

rootDimension是视图的布局参数

根据rootDimension来进行选择

  1. 如果rootDimension==ViewGroup.LayoutParams.MATCH_PARENT,表示根视图需要填充整个窗口,因此测量规格采用EXACTLY模式,并将测量尺寸设置为窗口大小(windowSize)。
  2. 如果rootDimension==ViewGroup.LayoutParams.WRAP_CONTENT,表示根视图需要根据内容自适应大小,因此测量规格采用AT_MOST模式,并将测量尺寸设置为窗口大小(windowSize)。
  3. 否则的话,根视图需要具体的固定大小,因此测量规格采用EXACTLY模式,并将测量尺寸设置为布局参数的值(rootDimension)。

这里的rootDimension感觉和MeasureSpec有点相似

  1. rootDimensionLayoutParams.MATCH_PARENT,则为精确模式,测量尺寸的大小就是窗口的大小。和MeasureSpec中的EXACTLY很像

  2. rootDimensionLayoutParams.WRAP_CONTENT,则为最大模式,大小不确定,但是测量尺寸的大小不能超过窗口的大小。和MeasureSpec中的AT_MOST很像

  3. default中的就是精确模式,大小为LayoutParams中指定的大小,你看最后传的是MeasureSpec.EXACTLY

    可是看了这么多,感觉还是不太理解windowSize和rootDimension分别表示的是什么

    我只知道传入getRootMeasureSpec中左边那个参数是windowSize,右边那个是rootDimension

    chatGpt中说到

    屏幕的大小是指设备屏幕的物理尺寸,例如手机屏幕的宽度和高度。

    而子视图的布局参数是指在布局文件(XML)中为视图指定的宽度(width)和高度(height)等属性,用于确定视图在父容器中的大小和位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h8486NVw-1684919910169)(../../assets/流程图-导出 (10)].png)

4.获得普通View的MeasureSpec相关源码

布局中的View的measure过程由ViewGroup传递过来,

先看一下ViewGroup中的measureChildWithMargins方法

4.1measureChildWithMargins方法源码

protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,
    int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

我们从中可以得到:

1.在调用子元素的measure方法之前会先通过getChildMeasureSpec得到子元素的MeasureSpec

2.子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,还与Viewmarginpadding有关

这时候我们查看getChildMeasureSpec里面的源码,看看它在里面干了些什么

4.2getChildMeasureSpec方法源码

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
   int specMode = MeasureSpec.getMode(spec);
   int specSize = MeasureSpec.getSize(spec);
	// 边距(外边距margin 内边距 padding) 最终影响是size
	// 且 size 最终在 childDimension 是 LayoutParams.MATCH_PARENT,保存到返回值上面
    int size = Math.max(0, specSize - padding);
    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            // Child wants a specific size... so be it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size, but our size is not fixed.
            // Constrain child to not be bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent asked to see how big we want to be
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

根据父容器的MeasureSpec同时结合View本身的LayoutParams确定子元素的MeasureSpec

参数中的padding为父容器已占用的空间的大小。

子元素的可用空间为父容器的尺寸大小减去padding

int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0,specSize - padding);

下图是普通View的MeasureSpec的创建规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-twxkqVEB-1684919910170)(../../assets/微信图片_20230522230913.jpg)]

当子View采用固定大小时,即第一行,不管父类ViewGroup多大,View的MeasureSpec都是精确模式,遵循它自身的LayoutParams

当子View为宽/高为match_parent,如果父容器的模式是精确模式,那么View也是精确模式,大小是父容器的剩余空间;如果父容器为最大模式,那么View也是最大模式,大小不会超过父容器剩余空间

当View的宽/高为wrap_content,不管父容器模式是最大模式还是精确模式,View都是不超过父容器剩余空间

UNSPECIFIED模式不进行考虑,因为它主要用于系统内部多次Measure情形

View的工作流程

1.Measure

1.1View的Measure

View的Measure动作是在measure方法中完成的,但是它的measure是由final修饰的,及它的方法不能进行重写。

但是在调用measure的时候,还会调用onMeasure

下面是onMeasure的源码

1.1.1onMeasure源码

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

虽然我们不知道这个是干什么的,但是

getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)

 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)

我们应该可以看出来,它会设置View的宽/高

所以我们看看getDefaultSize的源码

1.1.2getDefaultSize源码

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: //一般用于系统内部的测量,Vew的大小为getDefaultSize的第一个参数,即宽/高分别为	
                                  //getSuggestedMinimumWidth和getSuggestedMinimumHeight
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY://从这里可以看出,直接继承View的自定义控件,需要重写onMeasure方法并设置
                            //wrap_comtent时,自身大小。否则wrap_comtent 就相当与使用match_parent
        result = specSize;
        break;
    }
    return result; //返回测量后的大小,即MeasureSpec的getSize
}

我们先看下面的代码,因为在上面我们说过了

我们一般不考虑MeasureSpec.UNSPECIFIED,因为它主要用于系统内部多次Measure情形

所以我们就主要看的是MeasureSpec.AT_MOST

MeasureSpec.EXACTLY

result最开始的初值是getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()

MeasureSpec.EXACTLY中,将测量出来的specSize赋值给它

而如果是在MeasureSpec.AT_MOST中,result的值是还是getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()

那么getSuggestedMinimumWidth()和getSuggestedMinimumHeight()到底是什么呢?我们继续看它的源码

1.1.3getSuggestedMinimumWidth()与getSuggestedMinimumHeight()的源码

 protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
 }
//如果View没有设置背景,那么View的长度为mMinWidth(即android:minWidth这个属性指定的值,如果不指定为0)
//如果指定的背景,则为 max(mMinWidth, mBackground.getMinimumWidth())
 protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

 }

上下两个实现原理基本一样,所以就先讲上面这个

它先进行判断,如果view没有设置背景,则返回mMinWidth

mMinWidth对应于android:minWidth这个属性特指的值,一般不指定的话,那么它默认为0

如果view设置了背景,那它会判断是mMinWidth大还是**mBackground.getMinimumWidth()**更大

那么

mBackground.getMinimumWidth()是什么呢?

// mBackground.getMinimumWidth()
 public int getMinimumWidth() {
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;//返回的是Drawable的原始宽度
 }

可以看出getMinimumWidth()返回的是Drawable的原始宽度,前提是Drawable由原始宽度,如果没有原始宽度则返回0

否则返回原始宽度

1.1.4View的Measure的大体流程

所以所以我们可以理清思路,View的Measure的大体流程是:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eobySVId-1684919910170)(../../assets/流程图-导出 (14)].png)

1.1.5解决自定义view中wrap_content问题

直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时自身的大小,否则在布局中使用wrap_content就相当于使用match_partent。View在布局中使用wrap_content,那么它的specMode时AT_MOST模式,宽高=specSize,这种情况下View的specSize是parentSize,parentSize是父容器中目前可以使用的大小,这种效果和在布局中使用match_parent完全一致。
重写的代码

protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
    super.onMeasure(widthMeasureSpec,heightMeasureSpec);
    int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec);
    if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode== MeasureSpec.AT_MOST){
        setMeasuredDimension(mWidth,mHeight);
    }else if(widthSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(mWidth,heightSpecSize);
    }else if(heightSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(widthSpecSize,mHeight);
}

其中

	int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec);

其中知道它的Mode和测量出来的宽高,注意在ModeMeasureSpec.AT_MOST情况下,它测量出来的宽高就基本上等于ViewGroup的宽高了,

然后进行判断

  1. 如果它的宽和高都设置的是MeasureSpec.AT_MOST,就代表它的宽高都想设置为wrap_content,那么就给setMeasuredDimension传入mWidthmHeight
  2. 如果他得到宽设置成MeasureSpec.AT_MOST,则它的宽设置为wrap_content,那么setMeasuredDimension传入mWidthheightSpecSize
  3. 这个和2差不多

1.2ViewGroup的Measure

对于ViewGroup来说,除了完成自己的measure过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。ViewGroup是一个抽象类,因此没有重写View的onMeasure,但是提供了一个叫measureChildren的方法。

1.2.1measureChildren源码

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);  //调用子元素的measure
        }
    }
}

调用measureChildren()方法的时候,会对每一个child进行measure

1.2.2measureChild源码

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();//1.获取子元素的LayoutParams
//2.根据getChildMeasureSpec创建子元素的MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

//3.调用子元素的measure
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

它的作用就是获取子元素的LayoutParams,然后根据getChildMeasureSpec创建子元素的MeasureSpec,最后调用子元素的measure

getChildMeasureSpec在上面说过了,

就是这张表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rEWEoXEh-1684919910171)(../../assets/image-20230523102615877.png)]

在ViewGroup并没有定义其测量的具体的过程,ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类具体实现。
ViewGroup不像View一样对其onMeasure方法做统一实现,因为不同的ViewGroup子类有不同的布局特性,导致他们的测量细节各不相同。

1.3LinearLayout的onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);//竖直布局
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);//水平布局
    }
}

看看measureVertical的核心代码

1.3.1measureVertical源码

            // See how tall everyone is. Also remember max width.
      		for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            ···
            // Determine how big this child would like to be. If this or
            // previous children have given a weight, then we allow it to
            // use all available space (and we will shrink things later
            // if needed).
            final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight); //这个方法内部会调用子元素法measure方法,对子元素进行measure过程

            final int childHeight = child.getMeasuredHeight();
            if (useExcessSpace) {
                // Restore the original height and record how much space
                // we've allocated to excess-only children so that we can
                // match the behavior of EXACTLY measurement.
                lp.height = 0;
                consumedExcessSpace += childHeight;
            }

            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                   lp.bottomMargin + getNextLocationOffset(child)); //mTotalLength存储LinearLayout初步高度。每测量一个子元素,mTotalLength就会增加。

系统会遍历元素,并对每个元素执行 measureChildBeforeLayout方法,

这个方法内部会调用子元素法measure方法,对子元素进行measure过程并且系统会通过mTotalLength存储LinearLayout在竖直方向的高度的初步高度。每测量一次子元素,mTotalLength就会增加,增加的既包括了子元素的高度,又包括了竖直向上的margin

子元素测量完成后,LinearLayout会自己测量自己的高度

    mTotalLength += mPaddingTop + mPaddingBottom;
    int heightSize = mTotalLength;

    // Check against our minimum height
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

    // Reconcile our calculated size with the heightMeasureSpec
    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    ···
       setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
  //如果布局采用的match_parent或者具体数值,那么测量过程和View一致,高度为specSize
  //如果采用的是wrap_content,那么他的高度是所有子元素所占有的高度总和,但仍然不超过父容器的剩余空间。详见下面的源码

针对竖直方向LinearLayout,它的水平方向的测量过程遵循View的

竖着方向,如果是match_parent,则和view一致,如果是wrap_content,它的高度为所有子元素的所占有的高度的总和。仍然不超过父容器的剩余空间

我们看看resolveSizeAndState里面的源码,看看它是怎么做的

1.3.2resolveSizeAndState源码

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

其实,它和view的getDefaultSize

都是先对specMode进行判断

  1. 当specMode为MeasureSpec.AT_MOST且specSize < size,则将 result 设置为 specSize 并标记为 MEASURED_STATE_TOO_SMALL
  2. 当specMode为MeasureSpec.AT_MOST且specSize>=size,将View的size赋值给result
  3. 当specMode为MeasureSpec.EXACTLY,result = 测量出来的specSize
  4. 当specMode为MeasureSpec.UNSPECIFIED,表示 View 的大小没有限制,直接将 result 设置为 View 的大小。

1.3.3流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-988phmj9-1684919910171)(../../assets/流程图-导出 (15)].png)

1.4如何保证在Activity的生命周期类完成对View的测量

1.4.1Activity/View#onWindowFocusChanged

这个方法的含义是:View已经初始化完毕,宽/高已经准备好了,这个时候获取宽/高没有问题了。

onWindowFocusChanged会被调用好多次,当Activity的窗口得到焦点和失去焦点的时候均会被调用一次。当Activity继续执行和暂停执行的时候,onWindowFocusChanged均会被调用。

 @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus){
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight()}
    }

1.4.2view.post(runnable)

通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了

在Android中,View类的post(Runnable)方法可以将一个Runnable对象投递到视图所关联的消息队列的尾部。当Looper处理该消息时,会调用Runnable对象的run()方法。

 @Override
    protected void onStart() {
        super.onStart();
        view.post(new Runnable() {
            @Override
            public void run() {
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }

1.4.3ViewTreeObserver

使用ViewTreeObserer的众多回调可以完成这个功能。比如使用onGlobalLayoutListener这个接口,当View树状态或View树内部的View的可见性发生改变时,onGlobalLayout方法被回调。伴随View树的改变,onGlobalLayout方法将会被多次调用。

 @Override
    protected void onStart() {
        super.onStart();
        ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int width= view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }

1.4.4view.measure()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uqhnc9Et-1684919910171)(../../assets/image-20230524105528427.png)]

1.4.4.1match_parent

直接放弃,因为如果自定义的view为match_parent,那么构造MeasureSpec的时候,我们最后自定义的view父类的ViewGroup有关

需要知道父容器的剩余空间,但是我们没办法知道ViewGroup的剩余空间,所以也没办法做

1.4.4.2固定数值

如果为固定数值的话,自定义的view与它childSize有关,View的MeasureSpec都是精确模式,遵循它自身的LayoutParams

比如说现在我的宽/高 = 100px

那么我想得到它的宽和高我就可以通过代码

int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);

我当时在想,match_parent和wrap_content用这种**view.measure()**我到时能想的通,本来它们的宽/高就是未知数,测一下很正常。但是为固定数值的情况下,为什么还要测量,它不是已经给定了100px了嘛

但是

viewGroup不一样的话,虽然你的子view有100px,最后可能显示出来不是100Px,因为子view的大小不仅与自身的Layout有关,也与ViewGroup有关

1.4.4.3wrap_content
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT.MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT.MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);

为什么这么写了因为

View的尺寸使用30位二进制表示,最大是30个1(2^31-1),也就是(1<<30)-1

1.4.5总结

方法具体操作
onWindowFocusChanged重写 onWindowFocusChanged,当它失去焦点或者得到焦点的时候view.getMeasuredWidth()/view.getMeasureHeight()
view.post(runnable)post会将一个runnable对象发送到Looper的队尾,等Looper调用此Looper的时候,view也就初始化好了 view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
ViewTreeObserver使用onGlobalLayoutListener这个接口,当View树状态或View树内部的View的可见性发生改变时,onGlobalLayout方法被回调。伴随View树的改变,onGlobalLayout方法将会被多次调用。
view.measure()自己分析,三种情况下,进行判断

2.layout过程

ayout是确定View本身的位置,onLayout是确定子view的位置

2.1layout()源码

public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

 // 当前视图的四个顶点
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//通过setFrame来设定View的四个顶点位置
            							                       //即初始化mLeft,mTop,Bottom,mRight
            							                       //那么View在父容器中的位置也确定了

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        //接着调用onLayout方法
        //父容器确定子元素的位置,但View和ViewGroup中均没有真正实现。因为其具体实现和布局有关

     ····
}

大概就记住这段源码里面两个方法就行

setFrame();

通过setFrame来设定View的四个顶点位置,即初始化mLeft,mTop,Bottom,mRight,那么View在父容器中的位置也确定了

onLayout(changed, l, t, r, b);

这个就是父容器确定子元素的位置

2.2LinearLayout中的onLayout

LinearLayout中的onLayout和onMeasure差不多

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        layoutVertical(l, t, r, b);
    } else {
        layoutHorizontal(l, t, r, b);
    }
}

上面这个是LinearLayoutonLayout的源码


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);//竖直布局
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);//水平布局
    }
}

这个是LinearLayoutonMeasure的源码

和之前一样,我们看其中一个的源码

2.3layoutVertical源码

void layoutVertical(int left, int top, int right, int bottom) {
    ......
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();   //width和height实际上就是子元素的测量宽/高
            final int childHeight = child.getMeasuredHeight();

            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();

            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                            + lp.leftMargin - lp.rightMargin;
                    break;

                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;

                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }

            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }

            childTop += lp.topMargin;
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}

layoutVertical就是遍历所有的子元素,然后调用setChildFrame方法为子元素指定对应的位置,其中childTop会逐渐增大,后面的元素也就放在靠下的位置,setChildFrame仅仅只是调用了子元素的layout方法,这样父元素在layout完成定位后,通过onLayout,调用子View的layout方法,子View就会通过自己的layout方法确定自己的位置。

setChildFrame源码

private void setChildFrame(View child,int left,int top,int width,int height){
	child.layout(left,top,left+width,top+height);
}

2.4View的测量宽高和最终宽高有什么区别

即View的getMeasuredWidth/getMeasuredHeight和getHeight有什么区别?

测量宽高形成于View的measure过程,最终宽高形成于View的layout过程,日常开发中可认为两者相等,存在特殊情况会导致两者不一致。

如重写View的layout方法:

@Override
public void layout( int l , int t, int r , int b){
      // 改变传入的顶点位置参数
      super.layout(l,t,r+100,b+100)// 如此一来,在任何情况下,getWidth() / getHeight()获得的宽/高 总比 getMeasuredWidth() /      getMeasuredHeight()获取的宽/高大100px
     // 即:View的最终宽/高 总比 测量宽/高 大100px
   }

3.draw过程

将View绘制到屏幕上面
(1)绘制背景 background.draw(canvas)
(2)绘制自己 (onDraw)
(3)绘制children(dispatchDraw)
(4)绘制装饰 (onDrawScrollBars)

public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */

    // Step 1, draw the background, if needed
    int saveCount;

    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        drawAutofilledHighlight(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }

View绘制过程的传递通过dispatchDraw来实现,dispatchDraw会遍历调用所有子元素的draw方法
View有一个特殊的方法setWillNotDraw

/**
 * If this view doesn't do any drawing on its own, set this flag to
 * allow further optimizations. By default, this flag is not set on
 * View, but could be set on some View subclasses such as ViewGroup.
 *
 * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
 * you should clear this flag.
 *
 * @param willNotDraw whether or not this View draw on its own
 */
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

作用:设置WILL_NOT_DRAW 标记位
一个View不需要绘制任何内容,设置这个标记位为true后,系统会进行相应的优化。
默认情况下,View没有启用这个优化标记为,但ViewGroup会默认启用这个优化标记位。
对开发的实际意义:当我们自定义控件继承于ViewGroup并且本身不具备绘制功能,可以开启这个标记位从而便于系统进行后续的优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/564081.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何编写快速的SQL查询(一)——MySQL8.0优化器查询优化处理与样例

当希望MySQL能够以更高的性能运行查询时&#xff0c;最好的办法就是弄清楚MySQL是如何优化和执行查询的。一旦理解了这一点&#xff0c;很多查询优化工作实际上就是遵循一些原则让优化器能够按照预想的合理的方式运行。 MySQL是如何执行一个查询的过程的&#xff1f;根据图8-1可…

Java基础 变量与数据类型(类型转换)

变量 为什么需要变量 一花一世界&#xff0c;如果把一个程序看做一个世界或一个社会的话&#xff0c;那么变量就是程 序世界的花花草草、万事万物。即&#xff0c;变量是程序中不可或缺的组成单位&#xff0c;最基 本的存储单元。 初识变量 变量的概念 内存中的一个存储区域…

4.计算机网络基础

文章目录 1.网络互联模型2.常见网络协议&#xff08;1&#xff09;应用层协议&#xff1a;FTP、HFTP、HTTP&#xff08;2&#xff09;传输层协议&#xff1a;TCP、UDP三次握手、四次挥手&#xff08;重要&#xff09;TCP 和 UDP 区别&#xff08;1&#xff09;连接&#xff08;…

【可乐荐书】人工智能数学基础

本栏目将推荐一些经典的、有趣的、有启发性的书籍&#xff0c;这些书籍涵盖了各个领域&#xff0c;包括文学、历史、哲学、科学、技术等等。相信这些书籍不仅可以让你获得知识&#xff0c;还可以让你感受到阅读的乐趣和魅力。 今天给大家推荐的书籍是&#xff1a;《人工智能数…

1688阿里巴巴中国站按关键字搜索抓取新品数据API接口展示示例(封装可高并发)(Java系列)

一、电商平台上新的重要性 电商平台上新非常重要。 首先&#xff0c;持续的新品上线可以吸引更多的用户访问平台和留存用户的兴趣。新品可以激发用户想要知道更多、购买更多的欲望&#xff0c;从而提高用户的使用频率和转化率。此外&#xff0c;新品上线也可以使电商平台更具…

windows下lib文件中的函数列表查看

可以使用Visual Studio中自带的dumpbin工具&#xff0c;首先应确保该文件路径已经加载到了环境变量中&#xff0c;以博主环境为例&#xff0c;其路径在 D:\xxxxxxxxxxxxx\Microsoft Visual Studio 14.0\VC\bin dumpbin.exe /LINKERMEMBER libfile.lib

pytorch房价预测(线性回归)

文章目录 一、前言二、实现方法 一、前言 任务目标&#xff1a;根据统计在csv中的房屋属性相关数据&#xff0c;预测房屋最终成交价格数据集&#xff1a;《住宅属性数据集》&#xff0c;自取https://download.csdn.net/download/weixin_43721000/87785277 3.数据集字段解释&am…

JAVA开发(对大表数据逐条进行处理踩的坑记录一下)

一、编程语言介绍 所使用的编程语言为JAVA。纯后端开发。 二、炫技代码分享 现在我编写代码一般通过逆向工程生成&#xff0c;只需要设计好数据库表就可以逆向生成后端的接口模块。 三、案例分享 逆向工程。逆向工程涉及到的输出模块。具体运用需要关注和联系博主分享使用。 ##…

day82【Leetcode】

文章目录 前言一、检查替换后的词是否有效&#xff08;力扣1003&#xff09;二、有效的括号&#xff08;力扣20&#xff09;【1003类似题目】每日一题&#xff1a;数青蛙&#xff08;力扣1419&#xff09; 前言 1、检查替换后的词是否有效 2、有效的括号 3、数青蛙 一、检查替…

操作系统第三章——内存管理(中)

九月重楼二两&#xff0c;冬至蝉蜕一钱&#xff0c;煎入隔年雪煮沸&#xff0c;可治人间相思苦疾&#xff0c; 可是&#xff0c;重楼七叶一花&#xff0c;冬日何来蝉蜕&#xff0c;原是相思无解 殊不知 夏枯即为九叶重楼&#xff0c;掘地三尺寒蝉现&#xff0c;除夕子时雪&…

2022级云曦实验室考试(一)pwn

讲真&#xff0c;俺都不知道pwn是啥&#xff0c;等俺搜搜&#xff01; pwn简介&#xff1a; CTF中的pwn指的是通过通过程序本身的漏洞&#xff0c;编写利用脚本破解程序拿到主机的权限&#xff0c;这就需要对程序进行分析&#xff0c;了解操作系统的特性和相关漏洞&#xff0…

[PyTorch][chapter 35][Batch Normalize]

前言&#xff1a; Batch Norm 是深度学习里面常用的技术之一&#xff0c;主要作用是 把指定维度参数约束到范围内,有效的解决了梯度弥散 现象 ,有助于加速模型的训练速度。 问题解释 特征缩放 Feature Scaling Batch Normalization Torch API 一 问题解释 如上图,输入范…

《开箱元宇宙》爱心熊通过 The Sandbox 与粉丝建立更紧密的联系

你们有没有想过 The Sandbox 如何融入世界上最具标志性的品牌和名人的战略&#xff1f;在本期《开箱元宇宙》系列中&#xff0c;我们与 Cloudco Entertainment 的数字内容顾问 Derek Roberto 聊天&#xff0c;了解为什么爱心熊决定在 The Sandbox 中试验 web3&#xff0c;以及他…

Grpc 整合 Nacos SpringBoot 日常使用(Java版本)包括 Jwt 认证

前言 最近感到有点子迷茫&#xff0c;天天写业务代码有点麻木&#xff0c;趁着有点空闲时间去了解了下 Grpc 这个框架&#xff0c;一方面是听说他很火&#xff0c;支持多种语言。另一方面也是为了将来可能需要用到他&#xff0c;未雨绸缪一下&#xff0c;当然了本文只是基于使用…

Python数据可视化入门教程

什么是数据可视化&#xff1f; 数据可视化是为了使得数据更高效地反应数据情况&#xff0c;便于让读者更高效阅读&#xff0c;通过数据可视化突出数据背后的规律&#xff0c;以此突出数据中的重要因素&#xff0c;如果使用Python做数据可视化&#xff0c;建议学好如下这四个Pyt…

数据可视化是什么?怎么做?看这篇文章就够了

数据可视化是什么 数据可视化主要旨在借助于图形化手段&#xff0c;清晰有效地传达与沟通信息。也就是说可视化的存在是为了帮助我们更好的去传递信息。 我们需要对我们现有的数据进行分析&#xff0c;得出自己的结论&#xff0c;明确要表达的信息和主题&#xff08;即你通过…

https 建立连接过程

从真实的抓包开始 根据抓包结果可以看到 从客户端发起https 请求开始&#xff0c;主要经过以下几个过程&#xff1a; 1、tcp 三次握手 2、浏览器发送 Client Hello 到服务器 3、服务器对Hello 进行响应 4、服务器发送Server Hello 、证书、证书状态、服务器密钥&#xff0c;到…

【Linux服务】web基础与HTTP协议

web基础与HTTP协议 一、域名概述1.1域名空间结构1.2域名注册 二、网页的概念三、HTML概述3.1HTML超文本标记语言 四、Web概述4.1Web1.0与Web2.04.2静态网页4.3动态网页 五、HTTP协议概述5.1HTTP协议版本5.2http请求方法5.3GET 和 POST 比较5.4HTTP状态码5.5HTTP请求流程 一、域…

无代码开发:让程序员更高效,让非编程人员也能参与

说起无代码开发&#xff0c;可能大多数人的第一反应就是&#xff1a;“我不知道&#xff01;” 作为一种能快速实现复杂系统的软件开发模式&#xff0c;无代码开发目前还处于推广阶段。但在我们看来&#xff0c;无代码开发是一个很好的尝试&#xff0c;它能让程序员更高效&…

《汇编语言》- 读书笔记 - 第4章-第一个程序

《汇编语言》- 读书笔记 - 第4章-第一个程序 4.1 一个源程序从写出到执行的过程4.2 源程序程序 4.11. 伪指令1.1 segment ends 声明段1.2 end 结束标记1.3 assume 关联 2. 源程序中的“程序”3. 标号4. 程序的结构5. 程序返回6. 语法错误和逻辑错误 4.3 编辑源程序4.4 编译4.5 …