备注

原发表于2016.05.23,资料已过时,仅作备份,谨慎参考

前言

本文参考《Android 开发艺术探索》及网上各种资料进行撰写,目的是为自己理清 Android 中 View 的工作原理,复习学习内容,为后期阅读开源自定义 View 源码做好准备,深入学习可查看参考资料中的内容。

基本概念

本节介绍两个基本概念,为理解后面小节内容预热。

DecorView

DecorView 是 Window 中 View 的顶层 View,其结构如下所示:

DecorView 其实是一个 FrameLayout,其中包含了一个 LinearLayout,分为上下部分(两个 FrameLayout)。

我们在 setContentView 所设置的布局文件就是被加到下部分中 android.R.id.content 的 FrameLayout 中。

ViewRoot

ViewRoot 对应于 ViewRootImpl 类,是连接 WindowManager 和 DecorView 的纽带。

当 Activity 对象创建完毕后,会将 DecorView 添加到 Window 中,同时创建 ViewRootImpl 对象与 DecorView 建立关联。

View 的绘制流程是从 ViewRoot 的 performTraversals 方法开始的,其工作流程图如下所示:

performTraversals 方法的代码十分的长,我们先只看其中一小部分:

private void performTraversals() {
...
if (!mStopped) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) { // 获取测量规格,mWidth 和 mHeight 当前视图 frame 的大小
// lp是WindowManager.LayoutParams
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); // Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
...
}
}

这里调用了 performMeasure 开始进行测量,传入了两个 MeasureSpec 参数,我们先来学习一下 MeasureSpec 的概念。

MeasureSpec

概念

MeasureSpec 参与了 View 的 measure 过程,系统根据 MeasureSpec 来测量出 View 的测量宽/高。

  • 对于 DecorView,其 MeasureSpec 是由窗口尺寸和自身的 LayoutParams 来共同确定的
  • 对于普通 View,是由父容器的 MeasureSpec 和自身的 LayoutParams 来共同确定的

一个 MeasureSpec 由 SpecMode 和 SpecSize 组成,分别指测量模式和规格大小。

SpecMode 有三种模式:

  1. UNSPECIFIED,父容器不对 View 有所限制,要多大给多大,一般用于系统内部。
  2. EXACTLY,父容器检测出 View 所需的大小,这时 View 的最终大小就是 SpecSize 所指定的值。它对应于 View LayoutParmas 中的 match_parent 和具体数值。
  3. AT_MOST,父容器指定了一个可用的大小,View 的大小不能大于该 SpecSize。它对应于 LayoutParams 中的 wrap_content。

DecorView 的 MeasureSpec

对于 DecorView,在 ViewRootImpl.measureHierarchy() 方法中,有如下代码:

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

其中传入的参数 desiredWindowWidth 和 desiredWindowHeight 是屏幕的尺寸,下面再看一下 getRootMeasureSpec 的实现:

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

即是,根据 DecorView 的 LayoutParams 的宽/高的值,有以下情况

  1. LayoutParams.MATCH_PARENT:DecorView 确定宽/高为窗口大小
  2. LayoutParams.WRAP_CONTENT:大小不定,不能超出窗口大小
  3. 固定大小:确定宽/高为 LayoutParams 指定的大小

接着返回 measureSpec 以供下一步测量使用。performMeasure 方法会从 DecorView 开始,逐层往下进行测量。

普通 View 的 MeasureSpec

对普通的 View 的 measure 方法的调用,是由其父容器传递而来的,这里先看一下 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);
}

这里先获取了 子View 的 LayoutParams,与父容器的 MeasureSpec 一起生成了 子View 的 MeasureSpec,再调用 View 的 measure 方法。

再继续看,子View 的 MeasureSpec 是如何生成的:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec); 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;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

尽管代码稍微长一点,但逻辑还是很简单的,先将 size 减去 padding 和 margin 占用的控件,再根据父容器的 SpecMode 和 View 设置的 LayoutParams 来确定 View 的 measureSpec。

上述代码可简化为下图所示:

例如如果父容器的 measureSpec 是 AT_MOST 模式,View 的 LayoutParams 是 MATCH_PARENT,则 View 的 measureSize 为父容器可用大小,measureMode 与父容器相同为 AT_MOST。

上述表格是可凭逻辑进行推断的,所以只要看懂代码,无需死记硬背。另外 UNSPECIFIED 模式一般用于系统内部,故不需过多关注。

参考资料

View测量机制详解—从DecorView说起

android:padding和android:margin的区别

[旧][Android] View 工作原理(一)的更多相关文章

  1. [旧][Android] View 工作原理(二)

    备注 原发表于2016.05.27,资料已过时,仅作备份,谨慎参考 前言 本文大量参照<Android 开发艺术探索>及参考资料的内容整合,主要帮助自己理清 View 的工作原理.深入学习 ...

  2. android handler工作原理

    android handler工作原理 作用 便于在子线程中更新主UI线程中的控件 这里涉及到了UI主线程和子线程 UI主线程 它很特别.通常我们会认为UI主线程将页面绘制完成,就结束了.但是它没有. ...

  3. Android View框架总结(三)View工作原理

    转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52180375 测量/布局/绘制顺序 如何引起View的测量/布局/绘制? Perfor ...

  4. 【原创】Android View框架总结(三)View工作原理

    测量/布局/绘制顺序 如何引起View的测量/布局/绘制? PerformTraversales() ViewRoot View工作基本流程  MeasureSpec SpecMode Measure ...

  5. Android Widget工作原理详解(一) 最全介绍

    转载请标明出处:http://blog.csdn.net/sk719887916/article/details/46853033 ; Widget是安卓的一应用程序组件,学名窗口小部件,它是微型应用 ...

  6. Android ListView工作原理完全解析,带你从源码的角度彻底理解

    版权声明:本文出自郭霖的博客,转载必须注明出处.   目录(?)[+] Adapter的作用 RecycleBin机制 第一次Layout 第二次Layout 滑动加载更多数据   转载请注明出处:h ...

  7. Android ListView工作原理完全解析(转自 郭霖老师博客)

    原文地址:http://blog.csdn.net/guolin_blog/article/details/44996879 在Android所有常用的原生控件当中,用法最复杂的应该就是ListVie ...

  8. Android ListView工作原理全然解析,带你从源代码的角度彻底理解

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/44996879 在Android全部经常使用的原生控件其中.使用方法最复杂的应该就是 ...

  9. [旧][Android] LayoutInflater 工作流程

    备注 原发表于2016.06.20,资料已过时,仅作备份,谨慎参考 前言 感觉很长时间没写文章了,这个星期因为回家和处理项目问题,还是花了很多时间的.虽然知道很多东西如果只是看一下用一次,很快就会遗忘 ...

随机推荐

  1. 安装MySQL到Ubuntu 20.04

    本文的内容主要来自对How To Install MySQL on Ubuntu 20.04的翻译.在根据该文的指导下,我在自己的Ubuntu 20.04.3 LTS版本中安装了MySQL 8. St ...

  2. Maven 框架结构知识总结

    1.maven目录结构 目录 内容 ${basedir} 存放pom.xml和所有子目录 ${basedir}/src/main/java 项目Java代码 ${basedir}/src/main/r ...

  3. JDK并发工具类

    在JDK的并发包里提供了几个非常有用的并发工具类.CountDownLatch.CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线程 ...

  4. java-异常-异常处理原则

    1 异常处理的原则: 2 * 1,函数内部如果抛出需要检测的异常,那么函数上必须要声明. 3 * 否则必须在函数内用trycatch捕捉,否则编译失败. 4 * 5 * 2,如果调用到了声明异常的函数 ...

  5. 企业CICD规模化落地浅析

    本次分享的题目是<企业CICD规模化落地>,因此我们不会侧重讲解CICD是什么以及怎样做CICD,而是你已经知道怎样"玩转"CICD了,要如何在一个比较大的企业中规模化 ...

  6. linux 启动过程原理哦

    bios加电自检硬件设备 grub引导加载程序 当内核被加载到内存,内核阶段就开始了. init进程是所有进程的发起者和控制者.因为在任何基于unix的系统中,它都是第一个运行的进程. 然后执行sys ...

  7. Ubuntu 18.04 安装教程

    准备材料 Ubuntu安装U盘 足够的硬盘空间 未初始化的硬盘需要提前初始化 注意事项 Ubuntu安装盘的制作请参考我的另外一个博客,里面写清楚了怎么制作Ubuntu安装盘,步骤非常简单 请将要拿给 ...

  8. ApacheCN PythonWeb 译文集 20211028 更新

    Django By Example 中文版 1 创建一个博客应用 2 为博客添加高级功能 3 扩展你的博客应用 4 创建一个社交网站 5 分享内容到你的网站 6 跟踪用户动作 7 构建在线商店 8 管 ...

  9. Atcoder ARC-058

    ARC058(2020.7.4) A 从高到低依次填入能填的最小值即可. B 首先可以发现这个区间实际上只有横着的一条边有用,那么我们可以在边界上枚举中转点使得不经过非法区域即可. C 挺神的一道题. ...

  10. ARC下的内存管理

    1.ARC下单对象内存管理 局部变量释放对象随之被释放 int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = ...