作为一名 Android 开发,正常情况下对 View 的绘制机制基本还是耳熟能详的,尤其对于经常需要自定义 View 实现一些特殊效果的同学。

网上也出现了大量的 Blog 讲 View 的 onMeasure()onLayout()onDraw() 等,虽然这是一个每个 Android 开发都应该知晓的东西,但这一系列实在是太多了,完全不符合咱们短平快的这个系列初衷。

那么,今天我们就来简单谈谈 measure() 过程中非常重要的 MeasureSpec

对于绝大多数人来说,都是知道 MeasureSpec 是一个 32 位的 int 类型。并且取了最前面的两位代表 Mode,后 30 位代表大小 Size。

相比也非常清楚 MeasureSpec 有 3 种模式,它们分别是 EXACTLYAT_MOSTUNSPECIFIED

  • 精确模式(MeasureSpec.EXACTLY):在这种模式下,尺寸的值是多少,那么这个组件的长或宽就是多少,对应 MATCH_PARENT 和确定的值。
  • 最大模式(MeasureSpec.AT_MOST):这个也就是父组件,能够给出的最大的空间,当前组件的长或宽最大只能为这么大,当然也可以比这个小。对应 WRAP_CONETNT
  • 未指定模式(MeasureSpec.UNSPECIFIED):这个就是说,当前组件,可以随便用空间,不受限制。

通常来说,我们在自定义 View 的时候会经常地接触到 AT_MOSTEXACTLY,我们通常会根据两种模式去定义自己的 View 大小,在 wrap_content 的时候使用自己计算或者设置的一个默认值。而更多的时候我们都会认为 UNSPECIFIED 这个模式被应用在系统源码中。具体就体现在 NestedScrollViewScrollView 中。

我们看这样一个 XML 文件:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"> <TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
android:text="Hello World"
android:textColor="#fff">
</TextView> </android.support.v4.widget.NestedScrollView>

NestedScrollView 里面写了一个充满屏幕高度的 TextView,为了更方便看效果,我们设置了一个背景颜色。但我们从 XML 预览中却会惊讶的发现不一样的情况。

我们所期望的是填充满屏幕的 TextView,但实际效果却和 TextView 设置高度为 wrap_content 如出一辙。

很明显,这一定是高度测量出现的问题,如果我们的父布局是 LinearLayout,很明显没有任何问题。所以问题一定出在了 NestedScrollViewonMeasure() 中。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (this.mFillViewport) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != 0) {
if (this.getChildCount() > 0) {
View child = this.getChildAt(0);
LayoutParams lp = (LayoutParams)child.getLayoutParams();
int childSize = child.getMeasuredHeight();
int parentSpace = this.getMeasuredHeight() - this.getPaddingTop() - this.getPaddingBottom() - lp.topMargin - lp.bottomMargin;
if (childSize < parentSpace) {
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, this.getPaddingLeft() + this.getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentSpace, 1073741824);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
} }
}
}

由于我们并没有在外面设置 mFillViewport 这个属性,所以并不会进入到 if 条件中,我们来看看 NestedScrollView 的 super FrameLayoutonMeasure() 做了什么。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount(); final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear(); int maxHeight = 0;
int maxWidth = 0;
int childState = 0; for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
} // ignore something...
}

注意其中的关键方法 measureChildWithMargins(),这个方法在 NestedScrollView 中得到了完全重写。

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, this.getPaddingLeft() + this.getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, 0);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

我们看到其中有句非常关键的代码:

int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, 0);

NestedScrollView 直接无视了用户设置的 MODE,直接采用了 UNSPECIFIED 做处理。经过测试发现,当我们重写 NestedScrollView 的这句代码,并且把 MODE 设置为 EXACTLY 的时候,我们得到了我们想要的效果,我已经查看 Google 的源码提交日志,并没有找到原因。

实际上,绝大多数开发之前遇到的嵌套 ListView 或者 RecylerView 只展示一行也是由于这个问题,解决方案就是重写 NestedScrollViewmeasureChildWithMargins() 或者重写 ListView 或者 RecylerViewonMeasure() 方法让其展示正确的高度。

我起初猜想是只有 UNSPECIFIED 才能实现滚动效果,但很遗憾并不是这样的。所以在这里抛出这个问题,希望有知情人士能一起讨论。

每日一问:谈谈对 MeasureSpec 的理解的更多相关文章

  1. 每日一问:LayoutParams 你知道多少?

    前面的文章中着重讲解了 View 的测量流程.其中我提到了一句非常重要的话:View 的测量匡高是由父控件的 MeasureSpec 和 View 自身的 `LayoutParams 共同决定的.我们 ...

  2. 每日一问:谈谈 volatile 关键字

    这是 wanAndroid 每日一问中的一道题,下面我们来尝试解答一下. 讲讲并发专题 volatile,synchronize,CAS,happens before, lost wake up 为了 ...

  3. 每日一问:讲讲 Java 虚拟机的垃圾回收

    昨天我们用比较精简的文字讲了 Java 虚拟机结构,没看过的可以直接从这里查看: 每日一问:你了解 Java 虚拟机结构么? 今天我们必须来看看 Java 虚拟机的垃圾回收算法是怎样的.不过在开始之前 ...

  4. 每日一问:Android 消息机制,我有必要再讲一次!

    坚持原创日更,短平快的 Android 进阶系列,敬请直接在微信公众号搜索:nanchen,直接关注并设为星标,精彩不容错过. 我 17 年的 面试系列,曾写过一篇名为:Android 面试(五):探 ...

  5. 每日一问:你了解 Java 虚拟机结构么?

    对于从事 C/C++ 程序员开发的小伙伴来说,在内存管理领域非常头疼,因为他们总是需要对每一个 new 操作去写配对的 delete/free 代码.而对于我们 Android 乃至 Java 程序员 ...

  6. 每日一问:简述 View 的绘制流程

    Android 开发中经常需要用一些自定义 View 去满足产品和设计的脑洞,所以 View 的绘制流程至关重要.网上目前有非常多这方面的资料,但最好的方式还是直接跟着源码进行解读,每日一问系列一直追 ...

  7. 谈谈对XML的理解?说明Web应用中Web.xml文件的作用?

    谈谈对XML的理解?说明Web应用中Web.xml文件的作用? 解答:XML(Extensible Markup Language)即可扩展标记语言,它与HTML一样,都是SGML(Standard ...

  8. 12.谈谈this对象的理解

    1.谈谈this对象的理解? 2.this指向问题   Javascript理解this对象 this是函数运行时自动生成的一个内部对象,只能在函数内部使用,但总指向调用它的对象. 通过以下几个例子加 ...

  9. 「每日一题」面试官问你对Promise的理解?可能是需要你能手动实现各个特性

    关注「松宝写代码」,精选好文,每日一题 加入我们一起学习,day day up 作者:saucxs | songEagle 来源:原创 一.前言 2020.12.23日刚立的flag,每日一题,题目类 ...

随机推荐

  1. 『count 区间dp』

    count Description 既然是萌萌哒 visit_world 的比赛,那必然会有一道计数题啦! 考虑一个N个节点的二叉树,它的节点被标上了1-N的编号. 并且,编号为i的节点在二叉树的前序 ...

  2. 还不错的PHP导出EXCEL函数挺好用的

    直接上函数吧 //导出 $data内容二维数组 $title各个标题 $filename表名称 function exportexcelinfo($data=array(),$title=array( ...

  3. CTS,CLS,CLR解释

    问题阐述 CTS.CLS和 CLR分别是什么意思? 专家解答 CTS.CLS和 CLR是.NET框架的 3个核心部分,下面分别对它们进行介绍. (1)CTS CTS即通用类型系统,它定义了如何在.NE ...

  4. mssql 根据执行计划细节做优化操作

    示例: 1.如果select * 通常情况下聚集索引会比非聚集索引更优. 2.如果出现Nested Loops,需要查下是否需要聚集索引,非聚集索引是否可以包含所有需要的列. 3.Hash Match ...

  5. git commit 统计

    git log --author="username" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; lo ...

  6. PIE SDK缓冲区分析算法

    1.算法功能简介 缓冲区分析是指有点.线.面实体为基础,自动建立其周围一定宽度范围内的缓冲区多边形图层,然后建立该图层与目标图层的叠加,进行分析而得到的所需的结果.他是用来解决邻近度问题的控件分析工具 ...

  7. spring事务概念与获取事务时事务传播行为源码分析

    一.事务状态:org.springframework.transaction.TransactionStatus isNewTransaction 是否是新事务 hasSavepoint 是否有保存点 ...

  8. doucment的获取节点的信息

    document.activeElement 返回当前获取焦点元素 document.addEventListener() 向文档添加句柄 document.adoptNode(node) 从另外一个 ...

  9. JVM Code Cache空间不足,导致服务性能变慢

    本文阅读时间大约5分钟. 有业务反馈,线上一个应用运行了一段时间之后,在高峰期之后,突然发现处理能力下降,接口的响应时间变长,但是看Cat上的GC数据,一切都很正常. 通过跳板机上机器查看日志,发现一 ...

  10. elasticsearch安装中文分词器插件smartcn

    原文:http://blog.java1234.com/blog/articles/373.html elasticsearch安装中文分词器插件smartcn elasticsearch默认分词器比 ...