View 的测量过程中,有一个比较重要的类需要掌握:MeasureSpec。我们在阅读源码的时候会发现,在 View 的测量过程中,MeasureSpec 是一个会经常出现的类,如果不先掌握这个类的话,是没法阅读下去的。

MeasureSpec 会在很大程度上决定一个 View 的尺寸规格,之所以是很大程度上是因为这个过程还受父容器的影响,因为父容器影响 View 的 MesaureSpec 的创建过程。

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

MeasureSpec

MeasureSpec 是一个int值,但是这个int值被分为了两部分,一部分表示 SpecMode (测量模式),一部分表示 SpecSize(在某种测量模式下的大小)。

可能有很多人想不通,一个int型整数怎么可以表示两个东西(大小模式和大小的值),一个int类型我们知道有32位。而模式有三种,要表示三种状态,至少得2位二进制位。于是系统采用了最高的2位表示模式。如图:

最高两位是00的时候表示"未指定模式",即MeasureSpec.UNSPECIFIED。

最高两位是01的时候表示"'精确模式",即MeasureSpec.EXACTLY。

最高两位是10的时候表示"最大模式",即MeasureSpec.AT_MOST。

  • 精确模式(MeasureSpec.EXACTLY)

    在这种模式下,尺寸的值是多少,那么这个组件的长或宽就是多少。

  • 最大模式(MeasureSpec.AT_MOST)

    这个也就是父组件,能够给出的最大的空间,当前组件的长或宽最大只能为这么大,当然也可以比这个小。

  • 未指定模式(MeasureSpec.UNSPECIFIED)

    这个就是说,当前组件,可以随便用空间,不受限制。

MeasureSpec 通过将 SpecMode 与 SpecSize 打包成一个 int 值来避免过多的对象内存分配。为了方便操作,它还提供了对应的打包与解包方法。

// 将 size 与 mode 组合成一个 MeasureSpec 对象
android.view.View.MeasureSpec#makeMeasureSpec// 从 MeasureSpec 中获取 mode
android.view.View.MeasureSpec#getMode// 从 MeasureSpec 中获取 size
android.view.View.MeasureSpec#getSize

在 View 测量的时候,系统会将 LayoutParams 在父容器的约束下转换成对应的 MeasureSpec,然后再根据这个 MeasureSpec 来确定 View 测量后的大小。(这里需要注意,MeasureSpec 不是由 LayoutParams 唯一决定的,而是由 LayoutParams 与父布局一起决定的

各种 View 测量的区别

顶层 View

我们知道一般的 View 都会有父布局,但是最顶层的 View 是没有的,那么它是如何测量的呢?

首先它会获取 LayoutParams,再判断 LayoutParams 宽高的值:

  • 如果为 LayoutParams.MATCH_PARENT,这表示精确模式,大小就是窗口大小。
  • 如果为 LayoutParams.WRAP_CONTENT,这表示最大模式,大小未定,但是不能超过窗口大小。

这就比较简单了,顶层View的测量,一般宽高都是 LayoutParams.MATCH_PARENT,大小为窗口大小。

子 View

对于普通的 View 来说,它的测量与父布局有关,而每个父布局的特性又不同,无法每个都涉及到,所以这里采取一个“管中窥豹,可见一斑”的方法。

这里介绍一下 ViewGroup 的 measureChildWithMargins 方法。

android.view.ViewGroup#measureChildWithMargins

    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);}

方法中的 getChildMeasureSpec 方法比较长,就不贴代码了,下面会有文字说明。

getChildMeasureSpec 其实最后就是生成了一个 MeasureSpec 对象。

它的 size 由两部分决定,一个是 parentWidthMeasureSpec,一个是 lp.width / lp.height

具体的规则用表说明:

从这个表中我们可以看到,在构造 child 的 MeasureSpec 还是优先考虑了 child 自身的 size 的,特别是 child 直接要求一个固定的值的时候。

我们深入思考一下,比如我们经常使用到的 LinearLayout(竖向),它在决定 child 的大小的时,肯定不能让 child 的高度与自己的高度一样大,那么它是如何处理的呢?我们看看源码:

android.widget.LinearLayout#measureVertical

final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

可以看到,LinearLayout 并没有使用 ViewGroup 提供的 measureChild 方法,因为它并不符合 LinearLayout 的特性。那么哪一个布局符合呢???FrameLayout!!!

android.widget.FrameLayout#onMeasure

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int count = getChildCount();...for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (mMeasureAllChildren || child.getVisibility() != GONE) {measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);...}}...}

可以看到,它就是直接使用了 measureChildWithMargins 方法,因为它的特性只是做了一个层叠,并没有其他对 child 有其他限制,所以可以直接使用。

例子

说了这么多,我们来看看一个实际的应用场景吧。

我们知道,TextView 是有自己的 onMeasure 方法的。系统提供的 TextView,文字是从做到右的,那么我们现在想做这样的一个效果,将 TextView 的显示旋转一下,从上到下。如下图:

变成这样:

那么,有的同学就说话了,这旋转一下不就不可以了吗!是这样吗?在我们的屏幕中,不可能只会有一个 TextView,所以这个 TextView 很可能会与其他控件一起排列,当我们在旋转之前,假设它的宽与高是 100*300,那么旋转之后,它的高度变成了300,这个时候,由于父布局的限制,我们很可能看不到整个 TextView。而且我们在 xml 中也不好去预览它的效果。

那么下面,我们就来实现一下这个效果。

首先我们需要处理该控件的大小。

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.measure(heightMeasureSpec, widthMeasureSpec);setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());}

我们首先来看这个方法:super.measure(heightMeasureSpec, widthMeasureSpec);

这行代码非常容易引起误解,有的人就以为,我们将 widthMeasureSpec 与 heightMeasureSpec 换了一下,那么它测量出来的宽高自然就会互换。这是错误的理解!!!

比如,我们在 xml 中是这样使用这个自定义控件的:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:app="http://schemas.android.com/apk/res/com.yoog.widget"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity" ><com.yoog.widget.VerticalTextViewandroid:text="20:59"                              android:layout_width="wrap_content"android:layout_height="wrap_content" /></RelativeLayout>

我们假设20:59这串文字的长度为 300,高度为 100。父布局的宽高均大于 300。

所以,TextView 测量出来的就是 300*100,VerticalTextView 测量出来的值应该是 100 * 300。

但是,如果父布局的高度只有 250 的时候,横着测量的时候,是完全没有问题的,仍然测量出 300 * 100,但是竖着测量的时候,高度只能有 250,它需要换行,所以,结果是 200 * 250。

所以说,将 widthMeasureSpec 与 heightMeasureSpec 互换,只是为了正确的将父布局对 child 的影响正确的传递进去

由于,正确的传递了父容器的宽与高,走 TextView 的方法自然就会测量出正确的值,然后我们调用 setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth()); 方法,就可以将宽与高换过来了。

至于 onDraw 方法,我们就不深入了,只需要在 view 的左上角旋转一下画布就好了。

View 的 onMeasure 方法相关推荐

  1. 对View的onMeasure方法理解

    我们知道View在屏幕上显示出来要先经过measure和layout. 在调用onMeasure(int widthSpec, int heightSpec)方法时,要涉及到MeasureSpec的使 ...

  2. Android中自定义view的onMeasure()方法详谈

    背景 理解MeasureSpec MeasureSpec 情况分析 结合图例分析 总结 A little bit of progress every day!Come on! 背景 首先关于自定义vi ...

  3. android中对View的onMeasure()方法的理解

    在android开发中,很多人对自定义View是望而生畏,我也一样,但这又是向高级进阶的必经之路,主要是对View里面的很多方法不知道怎么理解,其中一个就是onMeasure()方法,网上有很多这样解 ...

  4. 自定义View之onMeasure()方法

    前言 一个View从创建到被绘制到屏幕上,需要完成measure(测量).layout(布置).draw(绘制)三个步骤,分别对应View中的measure().layout().draw()三个方法 ...

  5. Android View.onMeasure方法的理解

    View在屏幕上显示出来要先经过measure(计算)和layout(布局). 1.什么时候调用onMeasure方法?  当控件的父元素正要放置该控件时调用.父元素会问子控件一个问题,"你 ...

  6. View onMeasure 方法

    先看一篇博客 https://www.jianshu.com/p/3b6d0c17cdb0 再看这张图 image.png 1.这里的 AT_MOST.EXACTLY.UNSPECIFIED 分别对应 ...

  7. Andoid自定义View的OnMeasure详解和自定义属性

    一,OnMeasure详解 Android开发中偶尔会用到自定义View,一般情况下,自定义View都需要继承View类的onMeasure方法,那么,为什么要继承onMeasure()函数呢?什么情 ...

  8. Android横竖屏切换View设置不同尺寸或等比例缩放的自定义View的onMeasure解决方案(2)...

    Android横竖屏切换View设置不同尺寸或等比例缩放的自定义View的onMeasure解决方案(2) 附录文章1以xml布局文件方式实现了一个view在横竖屏切换时候的大小尺寸缩放,实现这种需求 ...

  9. View的invalidate()方法的源码分析

    首先要明白invalidate()方法是做什么的? View#invalidate(): /*** Invalidate the whole view. If the view is visible, ...

最新文章

  1. 2d的公式_西师大版六年级数学上册全册必背公式+高清版电子课文,收藏预习
  2. linux内核运行关系图,一张图看懂Linux内核运行交互关系
  3. java httpclient 异步请求_java_java实现HttpClient异步请求资源的方法,本文实例讲述了java实现HttpClien - phpStudy...
  4. Linux重要的热键[Tab]、[Ctrl]-c、[Ctrl]-d介绍
  5. 【初学】python执行系统命令四种方法比较
  6. 【解题报告】表达式求值(栈,表达式树)
  7. java什么是工作空间_[Java教程]Java开发工具(Eclipse工作空间的基本配置)
  8. linux设置自动关机命令,Linux怎么用命令设置自动关机
  9. python3 matplotlib多个子图分别对应不同colorbar
  10. win10命令行快捷键
  11. Simulink入门--创建简单模型
  12. 打开一个vb工程,弹出“visual sourcesafe login“对话框
  13. 分布式事务专题-基础概念(1)
  14. 手指 (shou zhi)
  15. 按键精灵X学习笔记(二):键盘命令
  16. PMP考试中常见的翻译问题
  17. STM32单片机蓝牙APP智能急救手表跌倒报警心率报警MAX30102
  18. 设计师:平面设计师、网页设计师、服装设计师、动画/3D设计师、UI设计师等设计师简介(工作内容、方向,知识储备)
  19. MySQL用户创建、登录等(超详细)
  20. 在Cadence16.6中导入Logo

热门文章

  1. Electron自定义软件卸载流程
  2. 身份证正则表达式验证
  3. Logrorate日志切割与备份,主机日志、Nginx日志备份
  4. C++获得程序运行时间
  5. 小巧好用 乐得瑞USB PD诱骗测试治具上手体验
  6. 最新版50个 Kubernetes(k8s) 生态工具
  7. 网易2017校园招聘笔试题 回文序列
  8. winform C# 可取消的超时任务(时间精度高)
  9. ipv6抓包 tcpdump_网络抓包工具tcpdump图文教程
  10. 制作一个二维码,扫描访问网页