博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
View学习(一)-DecorView,measureSpec与LayoutParams
阅读量:6295 次
发布时间:2019-06-22

本文共 8027 字,大约阅读时间需要 26 分钟。

这段时间在学习android中view的工作原理与自定义View的相关内容,所以未来这这几篇博客都总结一下相关的知识吧。

首先我们要了解和熟悉两个概念,DecorViewMeasureSpec.

DecorView

我们在设置Activity的界面时,用的就是这句话 setContentView(R.layout.activity_main),那么大家有没有疑问呢,这个名字有点奇怪啊,为什么是setContentView?难道不应该是setView吗?这个问题就要从DecorView来说起了。源码就不追踪了,直接说结论。

  1. DecorView就是我们的 Activity(或者整个Window界面)的最顶级View。
  2. DecorView extends FrameLayout,它里面只有一个子元素为 LinearLayout,代表整个 Window 界面。
  3. LinearLayout布局也包裹了两个FrameLayout
    • 上面的Framelayout是标题栏(TitleBarActionBar)。具体形式和android版本及主题有关。它默认包括了一个TextView,当然我们也可以自定义标题栏。
    • 下面的 FrameLayout 就是内容栏。它的id就是 content,我们通过setContentView所设置的布局文件其实就是添加在内容栏当中,所以这个方法就是叫做 setContentView

因此现在我们可以体会到这个方法的命名的确很好。所以我们开发的命名也尽量做到望名知意,有理有据.

There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton

现在我们就知道所谓的DecorView是怎么一回事了,google了一张图片可以比较清晰的表达这个问题。

918357-20170618233903462-2000797996.png

其中图片的绿色部分,就是我们在布局中写的 布局文件了。

默认标题栏只有一个TextView,我们也是可以自定义的。

//@author www.yaoxiaowen.com  三句话的顺序不要颠倒。    requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);    setContentView(R.layout.custom_title);    getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title_layout);

那我们如何得到content这个view呢。

ViewGroup content = (ViewGroup)findViewById(android.R.id.content);

那我们又如何得到我们添加的布局viewGroup呢?

//结合上一句代码content.getChildAt(0);

MeasureSpec

在讨论MeasureSpec之前,我们先回忆一下,在布局中,我们是怎么设置一个View的尺寸大小的。

//height类似,所以就不写了,仅拿 width举例子         android:layout_width="wrap_content"        android:layout_width="match_parent"        android:layout_width="100dp"

我们可以通过 LayoutParams来设置view的尺寸,但是仅依靠我们设置的参数,能完全决定view的尺寸吗? 肯定是不能的。因为一个View大小还要受到父ViewGroup的影响。

一个人的命运,既要靠自己奋斗, 也要考虑历史进程。所以一个view的尺寸大小,既要考虑自己所希望的大小,但是也要考虑父ViewGroup对其所施加的影响。否则如果View自己就能完全决定自己尺寸的大小,那真是没王法了。

MeasureSpec其实就是View当中的一个静态工具类,翻译过来就是 测量说明书。 代表了View在测试过程中所受到的约束。

View的测量过程中,系统将View自身的 LayoutParams结合父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个MeasureSpec来测量出View的尺寸宽高 。

MeasureSpec代表了一个32位的int类型的值。高两位是SpecMode(测量模式), 低30位是 SpecSize(尺寸规格大小)。将SpecModeSpecSize通过位操作封装成一个int值,可以减少内存分配,提升效率。 而MeasureSpec提供了打包,解包,toString等方法。可以方便操作。

精简后的源码如下:

//View#MeasureSpec public static class MeasureSpec {        private static final int MODE_SHIFT = 30;        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;               /**         * 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;  //0        /**         * 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;  //1073741824        /**         * 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;  //-2147483648        /**         *  根据尺寸和模式创建一个约束         */        public static int makeMeasureSpec(int size, int mode) {            if (sUseBrokenMakeMeasureSpec) {                return size + mode;            } else {                return (size & ~MODE_MASK) | (mode & MODE_MASK);            }        }        /**         * Extracts the mode from the supplied measure specification.         * 从约束规范中获取模式         */        public static int getMode(int measureSpec) {            return (measureSpec & MODE_MASK);        }        /**         * Extracts the size from the supplied measure specification.         * 从约束规范中获取尺寸         */        public static int getSize(int measureSpec) {            return (measureSpec & ~MODE_MASK);        }    }

SpecMode分为三类,具体的含义如下:

SpecMode 对应的布局参数 输出值 说明
UNSPECIFIED(未指明) 0 父容器不对子控件做任何限制,要多大给多大,该情况一般用于系统内部,表示一种测量状态
EXACTLY(精确) match_parent/具体值(dp,px) 1073741824 已经检测出View所需要的大小,此时View的最终大小就是SpecSize的值,注意,如果是match_parent,就是说明子控件把父容器剩下的尺寸都要了
AT_MOST(至多) wrap_content -2147483648 父容器并不知道子控件到底需要多大,但是它指定了一个可用的尺寸SpecSize,子控件的大小不能超过该值,该情况下,不同view有不同的默认实现方式,比如TextView就默认包裹所有字符

MeasureSpec与LayoutParams的对应关系

我们在前面就说过,一个View的尺寸受到父容器和本身LayoutParams双重影响。在进一步的讨论之前,我们先来看一个ViewGroup中的一个重要方法。

// #ViewGroup#getChildMeasureSpec     public static int getChildMeasureSpec(int spec, int padding, int childDimension)

看方法名字,就可以知道是测量子view的MeasureSpec,并且该方法是在ViewGroup中调用的。

它的三个参数意思如下:

  1. int spec : ViewGroup自身的spec,HeightMeasureSpecWidthMeasureSpec。(如果站在child的角度来看,就是父容器)。
  2. int padding : 如果是width,则是ViewGroup左右Padding+子View左右Margin+widthUsed。
    如果是height,则是ViewGroup上下Padding+子View上下Margin+heightUsed。
  3. int childDimension : 就是 child的LayoutParmams(lp.widthlp.height)。

该方法具体实现如下:

ViewGroup#getChildMeasureSpec

//此方法用来计算一个合适子视图的尺寸大小 (HeightMeasureSpec或者WidthMeasureSpec) public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        //得到父容器的size和mode        int specMode = MeasureSpec.getMode(spec);        int specSize = MeasureSpec.getSize(spec);        //当前ViewGroup剩余空间的大小。(站在子child的角度就是父容器剩余空间的大小).        //我们可以理解成 parentSize-padding        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;                //在ViewGroup的源码定义中, LayoutParams.MATCH_PARENT = -1            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size. So be it.                resultSize = size;                resultMode = MeasureSpec.EXACTLY;                //在ViewGroup的源码定义中, LayoutParams.MATCH_PARENT = -2            } 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);    }

该方法主要用来计算一个合适的子view的大小。里面是一系列的if else判断,其实它的内容,可以用一张表更加清晰的表现。

918357-20170618234001337-203688773.png

先记着这些内容,因为它在下面更近一步的讨论中有着非常重要的作用。

参考内容:

  • 任玉刚 《Anroid开发艺术探索》

作者:

github:

欢迎对于本人的博客内容批评指点,如果问题,可评论或邮件(yaowen369@gmail.com)联系

欢迎转载,转载请注明出处.谢谢

你可能感兴趣的文章
多级NUMA:AMD EPYC互连速率、位宽与功耗的关系
查看>>
Linux操作系统下以不同颜色命名的文件类型
查看>>
Spring(24)——自定义BeanDefinitionRegistryPostProcessor
查看>>
AngularJs 键盘事件和鼠标事件
查看>>
DC学院数据分析学习笔记(二):爬虫需要的HTML
查看>>
UWA平台新增【UI模块】和【粒子系统】检测功能!
查看>>
Oracle Study之--Oracle等待事件(2)
查看>>
Android开发者指南(15) —— Managing Virtual Devices
查看>>
查找、替换与定位
查看>>
解决莫名其妙出现connection closed的错误
查看>>
Linux多线程实践(3) --线程属性
查看>>
Catalyst3550交换机配置三层接口
查看>>
Elixir语言
查看>>
Java Calendar 类的时间操作
查看>>
esxi所连交换机划vlan导致vm不能通讯
查看>>
关于CE端口线路整改的建议
查看>>
如何禁止使用本地administrator进行共享连接
查看>>
用python解析html[SGMLParser]
查看>>
hive执行流程(3)-Driver类分析1Driver类整体流程
查看>>
Android开发学习笔记:对话框浅析
查看>>