简略理解Android View中的onMeasure和onLayout方法

本文仅简略地解释了onMeasure和onLayout的调用流程,不涉及调用过程中的参数意义的解释

假设当前Activity的页面层次架构如图[1]所示。一个LinearLayout中有两个TextView。

概述

总过程分两步:onMeasure过程和onLayout过程。onMeasure过程决定各个view的大小,onLayout过程决定view的位置。每个过程都从上往下地递归调用。

onMeasure过程

  • LinearLayout的父view/layout调用LinearLayout的measure方法,measure方法调用onMeasure方法。

  • onMeasure方法的目的是让layout(这里是LinearLayout)根据传入的两个MeasureSpec参数(代表父view当前分配给子view的Dimension),设置好自己‘想要’的Dimension大小

  • Linearlayout怎么知道自己“想要”多大呢?必须要先知道自己的子view的“想要”大小。于是它会调用各个子view的measure方法。当子View分配好自己的大小后,便开始计算自己的大小,最后调用setMeasuredDimension方法保存“想要”的Dimension。计算完毕后直接返回父View。

  • 值得注意的是,该过程(1-5)可能会调用多遍,来确认子view的大小(什么时候需要再次调用measure有待研究…)。

如图所示,LinearLayout首先要知道两个TextView的大小,然后根据结果调整自己的高度/宽度。

onLayout过程

  • LinearLayout的父view/layout调用LinearLayout的layout方法,layout方法调用onlayout方法。

  • 对于ViewGroup来说,onLayout方法的意义是“决定子view的具体位置”,即ViewGroup通过调用子view的layout方法,传入4个参数(top,left,right,bottom),子view应该按照这四个参数渲染自己(draw)。

如图所示,LinearLayout告诉第一个TextView它所在的位置。第二个TextView的位置应该是原位置加上第一个TextView的高度(还有一些边距等)。

最后

  • onMeasure和onLayout过程结束后,便开始递归调用onDraw方法渲染整个层次结构。
  • AbsoluteLayout的源码十分简单,可以参考它来学习上述两个方法。

[翻译]Android中View是如何绘制的

原文地址How Android Draws Views

Activity获得焦点时,会接收到绘制自身布局(layout)的请求。绘制布局的过程由Android内部框架负责,但需要Activity提供它的布局树的根节点信息。

绘制从布局(树)的根节点开始。(根节点)会接收到测量(measure)和绘制(draw)布局树的请求。绘制是通过遍历树节点并渲染和无效区域(invalid region)相交的视图来完成的。每一个ViewGroup向其子View发起绘制请求(通过调用其draw()方法),同时子View负责绘制自己。因为采用顺序方法来遍历布局树,所以父节点会先于子节点绘制,兄弟节点之间按顺序绘制。

系统框架仅对在无效区域的视图进行绘制。系统框架负责视图背景(View background)相关的绘制工作。

无效区域(invalid region):无效(invaild)意味着此区域所含的信息已经过期了,需要在下一个绘制循环(next draw())中被重绘。

你可以通过调用invalidate()方法来强制绘制一个View。绘制布局通过两个过程来完成:测量过程(measure pass)和布局过程(layout pass)。

  • 测量过程在measure(int, int)方法中实现,是一个自顶向下(top-down)遍历View树的过程。在递归过程中,每个View沿着树向下传达自身的尺寸规格(dimension specifications)。
  • 布局过程在layout(int, int, int, int)中实现,同样地也采用了自顶向下的遍历方法。遍历过程中,父节点负责利用在测量过程中获得的尺寸规格来决定其子节点的位置。

当某个View的measure()方法返回时,它以及它的所有子节点的getMeasuredWidth()getMeasuredHeight()方法的值就已经设置好了。一个View长和宽的测量值必须满足其父节点对其的约束。这保证了当某次测量结束前,所有父节点对子节点的约束被满足。

一个父View可能对其子View调用多次measure()方法。举个例子:父节点可能首先会通过一次没有明确尺寸约束(unspecified dimensions)的测量过程来获取每个子View想获得的视图大小。如果最后得到的数值过大或者过小,那么父节点会再次对其子View调用measure()方法,并使用实际的计算结果作为输入参数(即如果子View不同意首次测量结果,父View会进行第二次带约束条件的测量)。

如果要初始化一个布局过程,请调用requestLayout()方法。当一个View认为自己不再适合当前边界时,此方法会被调用。

测量过程使用了两个类来传递尺寸信息:ViewGroup.LayoutParamsMeasureSpec

ViewGroup.LayoutParams

子View中的ViewGroup.LayoutParams对象负责告知父View它想如何被测量和放置。ViewGroup.LayoutParams仅描述了View在长和宽上请求的大小。其值可以是以下所示的其中一种:

  • 一个确定的数字
  • MATCH_PARENT,意思是View请求跟其父View一样大(减去内边距后)
  • WRAP_CONTENT,意思是View请求其自身尺寸刚好能包含其内容即可(加上内边距后)

对于不同的ViewGroup子类,有不同的ViewGroup.LayoutParams子类与之对应。例如,RelativeLayout有专门的ViewGroup.LayoutParams子类,使其能够将子View水平或者垂直居中放置。

MeasureSpec

MeasureSpec对象在父子节点间传输尺寸约束时使用。MeasureSpec可以是以下三种类型中的一种:

  • UNSPECIFIED:用于父节点了解子节点所需尺寸的过程。例如,一个LinearLayout会使用高为UNSPECIFIED,宽为240作为参数调用measure()方法,以找出在240宽度下,子View所需要的总高度。
  • EXACTLY: 用于父节点强制指定一个尺寸值。其子节点必须使用这个尺寸值,而且要保证它所有的子节点适应这个值。
  • AT MOST: 用户父节点指定给一个尺寸值的上限。子节点必须保证其本身和子节点在此尺寸值范围内。

LEHome 智能家居开源方案 Android客户端

LEHome 是一套完整的开源智能家居方案。LEHome拥有以下特性:

  • 简单的控制命令编程
  • ibeacon室内定位
  • 高度模块设计
  • 红外控制、开关控制、传感器采集
  • android,web app,微信版客户端

项目地址:https://github.com/legendmohe/LEHome

本文主要介绍该方案的Android客户端

界面

主要界面有:

  1. 主界面,采用对话形式向服务端发送命令。
    主界面

  2. 收藏界面,主要是为了更方便地输入一些自定义的命令(message)。
    收藏

  3. 设置界面。
    设置1
    设置2

  4. 语音输入界面。
    语音输入

功能

  1. 基本功能

    1. 发送命令
      在输入框中输入命令,点击回车即可发送至服务器(连接正常情况下)。

    2. 语音发送命令
      切换至语音模式后,可用语音输入命令(使用百度语音,使用方法类似微信)。

    3. 设置参数
      设置一系列基本参数和增强功能的参数。

    4. 收藏命令语句
      可以添加删除一些自定义的语句。这些语句会出现在智能提示中。

  2. 增强功能

    1. 智能命令提示
      类似于IDE的智能提示,以列表形式提示下一个合法的命令有哪些。
      当输入字符>1个时,输入框左侧弹出智能提示(或补全)按钮,此时上划按钮看上一个提示,下划看下一个提示,左划弹出所有提示的列表。
      智能提示的类型有:补全当前命令、下一个合法的命令、日期、时间和收藏的命令语句。
      注意,使用此功能需要在设置菜单中下载补全数据。
      智能补全
      智能补全列表

    2. 本地、远程服务自动切换
      启用了此功能后,当手机处于跟服务器同一局域网时,会自动切换至本地模式。本地模式是指命令的发送和接受无需通过云端转发,直接在局域网内进行。

    3. 自动连接蓝牙麦克风
      启用了此功能后,当连接蓝牙耳机时,语音输入源路由至耳机麦克风端。

项目地址

https://github.com/legendmohe/LEHomeMobile_android