《Android内核剖析》读书笔记 第13章 View工作原理【View重绘过程】
计算视图大小的过程(Measure)
视图大小,准确的来说应该是指视图的布局大小;我们在layout.xml中为每个UI控件设置的layout_width/layout_height两个属性被用来设置父视图给当前视图分配的“窗口”大小,为了开发的方便和对不同屏幕分辨率的兼容适配对这两个参数的赋值一般都使用相对值(也可以使用具体值,比如100dp),比如WRAP_CONTENT/MATCH_PARENT,在代码中用常量-2/-1表示;计算视图布局大小的过程本质上就是把视图布局时使用的“相对值”转换成具体值的过程;
Measure递归调用过程
View系统启动measure过程是从ViewRootImpl中调用host.measure(…)开始的,参见下图:
从上图中可以看出measure过程主要就是从顶层父视图向子视图的递归调用view.measure(…),注意以下几点:
- View.measure(...)该方法是final的,不允许重载,View子类只能通过重载onMeasure(...)来完善自己的测量逻辑;
- MeasureSpec测量规格在measure过程中经常作为输入参数,该值为int型,其值由两部分组成,高16位代表规格模式specMode,低16位代表具体尺寸specSize;其中specMode只有三种值:
- MeasureSpec.EXACTLY:确定模式,即父视图希望子视图的大小是确定的,由specSize决定;
- MeasureSpec.AT_MOST:最多模式,即父视图希望子视图的大小最多是specSize指定的值;
- MeasureSpec.UNSPECIFIED:未指定模式,此时父视图完全尊重子视图的设计;
- 最顶层视图DecorView测量时的MeasureSpec从何而来呢?
是在ViewRootImpl中调用getRootMeasureSpec(..)获得,LayoutParam宽高参数均为MATCH_PARENT;
获得的specMode就是EXACTLY,specSize为物理屏幕大小; - 视图的布局大小由父视图和子视图共同决定;
layout.xml中对含有子视图的布局器中的layout_width/layout_height属性实际扮演了2个角色;一个是和父视图一起对布局器自身进行measure操作;另一个角色是作为其子视图的父视图参与子视图的measure操作; - ViewGroup类中提供了 measureChildWithMargins(…)方法,用来抽象和简化父子视图之间的padding、margin、实际内容区域间的测量和尺寸计算,让开发者可以无需关注这些公共的边界测量区域;
- 调用child.getLayoutParams(),并强制转换成MarginLayoutParams;只要是ViewGroup的子类,就要求LayoutParam继承于 MarginLayoutParams,即要在在generateDefaultLayoutParams()中返回MarginLayoutParams或者其子类,否则将无法在layout.xml中使用layout_margin参数,并且代码中不能使用measureChildWithMargins(…)方法,否则会抛强制类型转换错误;
- 分别就宽高调用getChildMeasureSpec(…);此时是传入父视图的测量规格,以及子视图在layout.xml中的设置值,返回的是子视图进行具体测量时的测量规格;父子视图之间的测量规格对应关系参见下图:
- 根据子视图自己的测量规格调用child.measure(…)进行最终的测量;
- 以上的逻辑由三方组成,包括父视图、开发者、子视图的设计者;父视图声明了自己可以提供的大小,而开发者在xml中使用layout_width/layout_height设置希望的大小,而视图设计者则进行最后的“拍板”,一般良好的视图设计者会根据子视图的layout_xx参数设置合适的布局大小,以尊重开发者的意图;
Layout递归调用过程
View系统启动layout过程是从ViewRootImpl中调用host.layout(…)开始的,参见下图:
从上图中可以看出layout过程也是从顶层父视图向子视图的递归调用view.layout(…),即父视图根据上一步measure子视图所得到的布局大小和布局参数,将子视图放在合适的位置上,布局参数核心指layout_gravity;注意以下几点:
- 与measure方法不同,View.layout(…)方法可被重载,ViewGroup.layout(…)为final的不可重载,ViewGroup.onLayout(…)为abstract的,子类必须重载,里面可以实现自己的位置分配逻辑;
- measure操作完成后得到的是对每个View经测量过的宽高measuredWidth/measuredHeight;
layout操作完成之后得到的是对每个View进行位置分配后的mLeft/mTop/mRight/mBottom; - layout_gravity一般出现于布局容器中,比如LinearLayout,他指的是当前容器内子视图的排列方式和顺序;
gravity一般出现于具体的View中,比如TextView,他指的就是TextView中实际文字的位置排放方式; - 凡是以layout_开头的布局参数基本都针对的是包含子视图的容器视图的,比如核心的layout_width/layout_height/layout_weight/layout_gravity,会在初始化LayoutParam时从xml中读取并转换为相应参数;
当对一个没有父容器的View设置相关layout_开头的属性时,实际上是没有任何意义的;
Draw递归调用过程
绘制过程就是把View对象绘制到屏幕上,如果该View是一个容器ViewGroup,则需要递归绘制其所包含的所有子视图;视图中可绘制的元素包括:
- View背景Backgroud:每个视图都可以有一个背景,背景可以是一个颜色值,也可以是一副图片,甚至可以是任何Drawable对象;
- 视图自身的内容:一般由视图设计者在视图的onDraw方法中完成具体的内容绘制,比如TextView的内容就是具体的文字,若是视图容器ViewGroup,则需要递归完成子视图的具体内容绘制;
只有这项内容是需要开发者设计并实现的,其余三项内容均由系统自动完成绘制; - 渐变边框FadingEdge:其作用是为了让视图的边框看起来更有层次感,其本质就是一个Shader对象,当然可以通过配置项关闭该效果;
- 滚动条ScrollBar:用来显示当前滚动的位置和状态,与PC不一样,该滚动条一般不能直接按住拖动;
View系统启动draw过程是从ViewRootImpl中调用host.draw(…)开始的,总体过程与measure/layout极其类似,即从顶级父视图开始向子视图进行递归调用view.draw(…),具体可参见下图:
下面就来看看这个过程中的几个核心方法都做了哪些具体的事情;
- ViewRootImpl.draw()内部流程参见下图:
里面要特别注意的一个核心点就是会根据根视图内部的Scroller对象来调用mScroller.computeScrollOffset()判断当前视图是否还处于滚动状态,若处于滚动状态,则会进行滚动偏移量计算,并且在最后再次调用scheduleTraversals()来发送一个异步重绘请求;
另外一点就是:Surface会按照底层驱动模式自动执行显卡模式或CPU模式;前者采用显卡来进行页面绘制,支持硬件图形加速,一般基于OpenGL实现;后者也被成为软件模式,即通过CPU及内存来模拟图形绘制,不支持硬件加速;目前的一些高端机型几乎都是显卡模式,低端机型更多采用CPU模式; - View.draw()内部流程主要就是为了完成前面提到的视图中的各种具体元素的绘制,大致过程如下:
- 绘制背景,由于可能会出现滚动条,所以绘制时可能会涉及到Canvas画布的平移和恢复,即 canvas.translate(scrollX, scrollY);
- 判断是否需要显示渐变框,若不需要则直接进入后续3、4、5绘制逻辑;
- 绘制视图自身内容,通过回调onDraw()实现;
- 调用dispatchDraw()绘制子视图,对于ViewGroup而言,默认已重载该方法,如有特别需求子类无需再次重载该函数;
- 调用onDrawScrollBars()绘制滚动条;
- ViewGroup.dispatchDraw()的作用是绘制父视图中包含的子视图,其本质就是给不同的子视图分配合适的画布Canvas,至于子视图具体如何绘制,则又会递归回调View.draw()方法;该方法内部将根据onLayout()中为子视图分配的具体区块调整Canvas的内部剪切区,从而让子视图认为画布是他自己独享的,坐标也是从(0,0)开始;其内部具体执行流程参见下图:
有几点需要注意:- 区分View动画和ViewGroup布局动画:前者指的是View自身的动画,可以通过setAnimation(.)添加;而后者是专门针对ViewGroup而言的,指的是该ViewGroup在显示内部的子视图时而设置的动画,可以在layout.xml中对容器标签设置layoutAnimation属性,比如可以对LinearLayout设置子视图在显示时出现逐行显示、随机显示、或落下等不同的动画效果;
- 在获取画布剪切区时会自动处理掉padding,让子视图获取的画布无需关注这些附加逻辑;
- 默认情况下子视图的ViewGroup.drawChild()绘制顺序与子视图被添加的顺序一致,但开发者可以重载ViewGroup.getChildDrawingOrder()方法提供不同的顺序;
- 当给一个子视图添加了移除动画时,该子视图会被添加到mDisappearingChildren队列中,在动画结束之前该子视图将一致存在,但此时该子视图无法被点击,也无法获得任何消息事件,仅仅是可见而已;
- ViewGroup.drawChild()的核心过程是为子视图分配合适的Canvas剪切区,其大小取决于child的布局大小,位置取决于child的内部滚动值、以及当前动画,其内部流程参见下图:
有几点需要注意:- 该方法在新的版本(比如4.2.2)中已经被放在View中了,ViewGroup中直接中转调用child.draw(canvas, this, drawingTime);
这是在看android源码时需要特别注意的,View/ViewGroup之间有很多方法重载,且存在父子视图的递归调用,经常会把人搞晕,看代码时需要非常平静才行; - 对于ViewGroup中子视图的动画支持有两种方式,一种是通过setAnimation(.)添加,另外一种是通过重载ViewGroup.getChildStaticTransformation(View child, Transformation t)方法实现;
注意:这里使用的这种代码设计方式我个人觉得是很不优雅的,有点反模式的味道,他在ViewGroup中声明了一个变量 mChildTransformation,是一个空的Transformation实例,然后在View.draw(…)方法中使用时将该变量传递给上面的需要开发者重载的getChildStaticTransformation方法,开发者需要在方法内对输入参数t进行具体动画参数赋值,并return true,标示采用该静态回调方法来处理子视图的动画;
更符合常规的设计思路不应该是让开发者直接重载getChildStaticTransformation(View child);方法并返回具体的Transformation实例吗?
- 该方法在新的版本(比如4.2.2)中已经被放在View中了,ViewGroup中直接中转调用child.draw(canvas, this, drawingTime);
- View.onDrawScrollBars()的作用是绘制滚动条;
- 滚动条是View的基本元素之一,每个View都可以有滚动条,只是某些视图从用户体验的角度无需显示而已,可以在layout.xml中通过scrollBarXXX相关的属性设置滚动条;
- 滚动条可以是水平的、垂直的、或者两者均有,滚动条的展示封装在ScrollBarDrawable中,包括三个基本尺寸range/offset/extent、以及用来标识滚动条背景和自身的两个图片track/thumb;
range:代表滚动条从头至尾滚动过程中所跨越的范围有多大,比如你想用滚动条来标示一万行代码,那range就可以设置10000;
offset:代表滚动条当前的偏移量,或者是可视的第一行在整个滚动跨度的什么位置,比如当前已经滚动到600行了,那offset就是600;
extent:代表显示滚动条的视图View在屏幕上的可视高度或宽度,比如200dp;
track:代表滚动条显示的背景或轨道,一般其宽高度和extent一致;
thumb:代表滚动条显示的具体前景,其宽高度、位置会根据range/offset/extent三者的具体值进行计算获得;
以上描述的几个变量具体展示参见下图:
- 滚动条一共有三种状态ON/OFF/FADDING,即显示、隐藏、正在隐藏(处于显示状态,但通过设置透明度悄悄改变状态);
一般而言,从用户体验的角度来说滚动条会在滚动完毕后自动隐藏,而滚动时自动显示,开发者可以通过scrollBarFadeDuration参数设置自动隐藏间隔时间,也可以调用setScrollbarFadingEnabled()设置是否自动隐藏; - 开发者可重载3个computeVerticalScrollXXX()方法来实现对滚动条具体显示位置和thumb大小的控制,可以调用awakenScrollBars()方法强制显示滚动条;
自定义滚动位置时可调用scrollTo()/scrollBy()来完成滚动到具体位置、或者滚动具体距离;
以上内容若有转载,请注明出处,欢迎访问
老唐的专栏http://blog.csdn.net/sfdev
《Android内核剖析》读书笔记 第13章 View工作原理【View重绘过程】的更多相关文章
- Android内核剖析读书笔记
第16章 程序包管理 PackageManagerService類 PmS 目錄 16.1 包管理概述 16.2 packages.xml文件格式 16.3 包管理服務的啟動過程 16.4 應用程序的 ...
- Android内核剖析读书笔记(1)—Framework概述
一.Framework组成 1.服务端组成 a.WindowManagerService 决定各窗口的叠放次序.隐藏或者显示窗口 b.ActivityManagerService 管理应用 ...
- 《Android内核剖析》读书笔记 第13章 View工作原理【View树遍历】
View状态分类 在View视图中定义了多种和界面效果相关的状态,比如拥有焦点Focused.按下Pressed等,不同的状态一般会显示不同的界面效果,而且视图状态会随着用户的操作而改变,一般通过xm ...
- APUE读书笔记-第13章-守护进程
第13章 守护进程 13.1 引言 *守护进程也称精灵进程(daemon)是生存期较长的一种进程.它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNI ...
- STL源码剖析读书笔记--第四章--序列式容器
1.什么是序列式容器?什么是关联式容器? 书上给出的解释是,序列式容器中的元素是可序的(可理解为可以按序索引,不管这个索引是像数组一样的随机索引,还是像链表一样的顺序索引),但是元素值在索引顺序的方向 ...
- Linux内核分析 读书笔记 (第一章、第二章)
第一章 Linux内核简介 1.1 Unix的历史 Unix很简洁,仅仅提供几百个系统调用并且有一个非常明确的设计目的. 在Unix中,所有东西都被当做文件,这种抽象使对数据和对设备的操作是通过一套相 ...
- C++ primer plus读书笔记——第13章 类继承
第13章 类继承 1. 如果购买厂商的C库,除非厂商提供库函数的源代码,否则您将无法根据自己的需求,对函数进行扩展或修改.但如果是类库,只要其提供了类方法的头文件和编译后的代码,仍可以使用库中的类派生 ...
- JavaScript高级程序设计第三版-读书笔记(1-3章)
这是我第一次用markdown,也是我第一次在网上记录我自己的学习过程. 第一章 JavaScript主要由以下三个不同的部分构成 ECMAScript 提供核心语言功能 DOM 提供访问 ...
- $《第一行代码:Android》读书笔记——第13章 Android高级技巧
(一)全局获取Context 1.创建ApplicationUtil类继承自Application类: public class ApplicationUtil extends Application ...
随机推荐
- Delphi的DLL里如何实现定时器功能?
一,首先引入“mmsystem”单元. 二,启动定时器: var MMTimerID: Integer; // 定时器ID MMTimerID := timeSetEvent(1000, 0, @Ti ...
- Linux IP代理筛选系统(shell+proxy)
代理的用途 其实,除了抓取国外网页需要用到IP代理外,还有很多场景会用到代理: 通过代理访问一些国外网站,绕过被某国防火墙过滤掉的网站 使用教育网的代理服务器,可以访问到大学或科研院所的内部网站资源 ...
- TCP/IP笔记 二.网络层(2)——ICMP,RIP,OSPF,BGP
1. ICMP ICMP (Internet Control Message Protocol) 作用:提高 IP 数据报交付成功的机会. 1.1 特点 ICMP 允许主机或路由器报告差错情况和提供有 ...
- Trufun云端建模平台之云端UML工具发布
Trufun云端建模平台包括云端UML工具,云端BPMN工具,云端思维导图工具. 云端UML工具是目前最先进的基于HTML5的UML2.x建模工具,所有代码基于JAVA开发,支持类图.用例图.活动图. ...
- 《生活在Linux中》之:使用Bash就是使用Emacs
定义bash Emacs模式下的快捷键请参考: Readline-在BASH下自定义键盘热键 未完待续...
- php 和thinkphp 对excel操作
php对excel的操作主要通过引入 excel_reader2.php 或者是PHPExcel 类进行 两个文件自行下载 php 对其读操作: 文件目录结构 excel_reader2.php ...
- ExtJs4 笔记(6) Ext.MessageBox 消息对话框
本篇演示消息对话框的用法,ExtJs封装了可能用到的各类消息框,并支持自定义的配置. 如下是用到的html: [html] <h1>各种消息框</h1> <div id= ...
- 窗体透明,但窗体上的控件不透明(简单好用)good
1.在Delphi中,设置窗体的AlphaBlend := true;AlphaBlendValue := 0-255; AlphaBlendValue越小窗体的透明度就越高.这种方法将会使窗体和窗体 ...
- C# Http以文件的形式上传文件
以下的是上传的方法: // <summary> /// 将本地文件上传到指定的服务器(HttpWebRequest方法) /// </summary> /// <para ...
- 北京哪儿有卖tods豆豆鞋的?在线等答案、、、、(类似动物园、西单等地)_百度知道
北京哪儿有卖tods豆豆鞋的?在线等答案....(类似动物园.西单等地)_百度知道 北京哪儿有卖tods豆豆鞋的?在线等答案....(类似动物园.西单等地)