IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> MeasureSpec源码解读 -> 正文阅读

[移动开发]MeasureSpec源码解读

今天来讲讲MeasureSpec吧。因为他与View的测量流程相关性很大,只有正确的理解了MeasureSpec的工作原理,我们才能更好的自定义View。那么MeasureSpec它的作用是什么呢?一般来说,它在很大程度上决定了一个View的尺寸规格。为什么是很大程度上呢?因为一个View的尺寸大小还可能和布局树中的父布局相关。想一想,如果你在你的子View中的布局参数写了match_parent,那么这个控件的大小是不是就和父布局相关了?所以说,我们今天的内容主要有以下两个方面,一个是MeasureSpec的源码,一个是MeasureSpec是怎么配合布局参数去测量View的大小的。

MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式而SpecSize是指在某种测量模式下的规格大小。下面先看一下 MeasureSpec的源码,通过下面的代码,应该不难理解MeasureSpec的工作原理

    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        @UnsupportedAppUsage
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }
    }

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

SpecMode主要有三类,每一类都有特殊的含义,如下表所示

测量模式意义
UNSPECIFIED父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。
EXACTLY父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。
AT_MOST父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。

看完了MeasureSpec的源码,那么接下来就是MeasureSpec怎么与布局参数配合去测量了。在View 测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽/高。**MeasureSpec不是唯 一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。**这句话其实很好理解,毕竟子View是在父容器里面的,不可能子View要求什么就是什么。打个比方,假设你向你父亲要500w去买房,但是你父亲没有500w,只有100w,虽然他想给你500w,但是这在它给不了你那么多。所以说子View的测量还是需要结合父view的MeasureSpec。那么怎么决定的呢?下面就来解决这个问题。其实这个问题在android的viewGroup源码中有答案了,我们可以看一下ViewGroup的源码,getChildMeasureSpec这个方法,顾名思义这个方法是得到子View的MeasureSpec,而上面我们讲过,MeasureSpec里面包含了SpecMode和SpecSize,其中SpecSize就是子View的大小了。所以我们看一下是怎么得到子View的MeasureSpec

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //这里得到的是父容器的specMode和specSize
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

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

    int resultSize = 0;
    int resultMode = 0;
	//根据父容器不同的specMode,子View的测量方式也不一样
    switch (specMode) {
        //如果父容器的specMode是EXACTLY    
        case MeasureSpec.EXACTLY:
            //childDimension>=0表示布局参数是具体的数值
            if (childDimension >= 0) {
                //这是时候子View的大小,就是子View要求的大小,如果超出父容器的空间怎么办?这里不同的ViewGroup会有不同的实现,如果是LinearLayout,那么最多只会显示父View的空间大小。
                resultSize = childDimension;
                //无论如何,子View是确定大小的,所以它的specMode就是EXACTLY的
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //这是时候子View的布局参数是MATCH_PARENT,那么子View的空间大小就是剩余空间大小了。那么这个剩余空间大小是多少? Math.max(0, specSize - padding),最终算出来也是一个准确的值,所以它的specMode还是是EXACTLY的
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //这是时候子View的布局参数是WRAP_CONTENT,那么子View的空间大小自己测量的大小了,注意这里需要二次测量才能最终知道子View的大小。但是无论子View的大小是多少,他都不能超过父容器的剩余空间,所以,这时候它的specMode就是AT_MOST了。以上就是父控件时EXACTLY的时候,获取子View的MeasureSpec的算法了,这种算法更重要的是理解
                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);
}

上面的算法,大家自己去写一下自定义ViewGroup就知道怎么回事了,后面两个具体就不讲了,因为如果理解了第一种情况,后面两种情况就是一样的道理的。

直接通过一个表格来总结一个父容器的MeasureSpec和子View的布局参数怎么决定子View的MeasureSpec的吧。
在这里插入图片描述
以上就是MeasureSpec的内容了,不难,但是需要大家去理解,只有理解了后续的自定义View才能更加的熟练。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-04-24 09:34:21  更:2022-04-24 09:36:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 23:40:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码