ViewRoot,DecorView,MeasureSpec和View的工作原理——Android开发艺术探索笔记
原文链接 http://sparkyuan.me/ 转载请注明出处
View的绘制流程是从ViewRoot的performTraversals方法開始的。它经过measure、layout和draw三个过程才干终于将一个View绘制出来,当中measure用来測量View的宽和高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。
ViewRoot和DecorView
ViewRoot
- ViewRoot相应ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均通过ViewRoot来完毕。
- ActivityThread中。Activity创建完毕后,会将DecorView加入到Window中,同一时候创建ViewRootImpl对象,并建立两者的关联。
DecorView
- DecorView作为顶级View,普通情况下它内部包括一个竖直方向的LinearLayout,在这个LinearLayout里面有上下两个部分(详细情况和Android版本号及主体有关),上面的是标题栏,以下的是内容栏。在Activity中通过setContentView所设置的布局文件事实上就是被加到内容栏之中的,而内容栏的id是content,在代码中能够通过ViewGroup content = (ViewGroup)findViewById(R.android.id.content)来得到content相应的layout。
- DecorView事实上是一个FrameLayout,View层的事件都先经过DecorView。然后才传递给我们的View。
MeasureSpec
在測量过程中,系统会将View的LayoutParams依据父容器所施加的规则转换成相应的MeasureSpec。然后再依据这个MeasureSpec来測量出View的宽和高。測量出来的宽和高不一定等于View终于的宽和高。
MeasureSpec将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配。高2位代表SpecMode,低30位代表SpecSize,SpecMode是指測量模式。而SpecSize是指在某种測量模式下的规格大小。
SpecMode有三类:
- UNSPECIFIED:父容器不正确View有不论什么限制,要多大给多大。这样的情况一般用于系统内部。表示一种測量状态
- EXACTLY:父容器已经检測出View所须要的精确大小,这个时候View的终于大小就是SpecSize所指定的值。它相应于LayoutParams中的match_parent和详细的数值这两种模式
- AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,详细是什么值要看不同View的详细实现。它相应于LayoutParams中的wrap_content。
普通MeasureSpec的创建规则
对于普通View。其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。
- 子View为精确宽高,不管父容器的MeasureSpec,子View的MeasureSpec都为精确值且遵循LayoutParams中的值。
- 子View为match_parent时,假设父容器是精确模式,则子View也为精确模式且为父容器的剩余空间大小;假设父容器是最大模式。则子View也是最大模式且不会超过父容器的剩余空间。
- 子View为wrap_content时。不管父View是精确还是最大模式,子View的模式总是最大模式。且不会超过父容器的剩余空间。
View的工作流程
measure
ViewGroup的measure方法会遍历每一个子元素,并调用子元素内部的measure方法,measure源代码例如以下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
注:
- getDefaultSize()返回MeasureSpec中的specSize,也就是View測量后的大小。
- getSuggestedMinimumWidth(),View假设没有背景,那么返回android:minWidth这个属性指定的值,这个值能够为0;假设设置了背景,则返回背景的最小宽度和minWidth中的最大值。
- getSuggestedMinimumHeight(),与getSuggestedMinimumWidth()相似。
- 直接继承View的自己定义控件须要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content时就相当于使用match_parent。
由于LayoutParams=wrap_content的情况下,MeasureSpec为AT_MOST。所以View的宽和高为父容器当前剩余的空间,这样的效果与match_parent一致。详细处理方法要依据需求灵活决定。
怎样得到View的宽和高
在Activity的onCreate、onStart、onResume方法中均无法正确得到某个View的宽/高信息。这是由于View的measure过程和Activity的生命周期方法不是同步运行的,因此无法保证Activity运行了onCreate、onStart、onResume时某个View就已经測量完毕了,假设View还没有測量完毕,那么获得的宽/高就是0。
能够通过例如以下四个方法来解决问题:
- Activity或者View的onWindowFocusChanged方法(注意该方法会在Activity Pause和resume时被多次调用)
- view.post(new Runnable( {@Overidde public void run(){})}),在run方法中获取。
- ViewTreeObserver中的onGlobalLayoutListener中。
- 手动调用View的measure方法。
演示样例代码请參考原书P190页
layout
layout的作用是用来确定子视图在父视图中的位置。源代码例如以下:
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
if (mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
通过setFrame()确定四个顶点的位置,进而确定View在父容器中的位置。
在View的默认实现中,View的測量宽/高和终于宽/高是相等的,仅仅只是測量宽/高形成于View的measure过程,而终于宽/高形成于View的layout过程。即两者的赋值时机不同,測量宽/高的赋值时机略微早一些。多数情况下能够觉得View的測量宽/高就等于终于的宽/高,但对于在View的layout中改变了View的left、top、right、bottom四个属性时,得出的測量宽/高有可能和终于的宽/高不一致。
draw
draw的过程非常easy主要有以下几步:
- 绘制背景(background.draw)
- 绘制自己(onDraw)
- 绘制children(dispatchDraw)
- 绘制装饰(onDrawScrollBars)。
源代码例如以下
public void draw(Canvas canvas) {
/ * Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background if need
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children (dispatchDraw)
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
//Step 1, draw the background, if needed
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
// Step 2, save the canvas' layers
...
// Step 3, draw the content
if (!dirtyOpaque)
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
}
注:
- View有一个特殊的方法setWillNotDraw。假设一个View不须要绘制不论什么内容,设置这个标记位true后,系统会进行优化。默认情况下,View没有启用这个优化标记位,可是ViewGroup会默认启用这个优化标记位。
- 这个标记位对实际开发的意义是:假设自己定义控件继承于ViewGroup而且本身不具备绘制功能时,就能够开启这个标记位从而便于系统进行兴许的优化。当明白知道一个ViewGroup须要通过onDraw来绘制内容时,须要显示地关闭WILL_NOT_DRAW这个标记位。
欢迎转载。转载请注明本文链接http://blog.csdn.net/l664675249/article/details/50774617
ViewRoot,DecorView,MeasureSpec和View的工作原理——Android开发艺术探索笔记的更多相关文章
- Android开发艺术探索笔记——View(二)
Android开发艺术探索笔记--View(二) View的事件分发机制 学习资料: 1.Understanding Android Input Touch Events System Framewo ...
- Android开发艺术探索笔记—— View(一)
Android开发艺术探索笔记 --View(一) View的基础知识 什么是View View是Android中所有控件的基类.是一种界面层控件的抽象. View的位置参数 参数名 获取方式 含义 ...
- Android中View的事件分发机制——Android开发艺术探索笔记
原文链接 http://sparkyuan.me/ 转载请注明出处 介绍 点击事件的事件分发就是对MotionEvent事件的分发过程.当一个MotionEvent产生了以后,系统须要把这个事件传递给 ...
- 《android开发艺术探索》读书笔记(四)--View工作原理
接上篇<android开发艺术探索>读书笔记(三) No1: View的三大流程:测量流程.布局流程.绘制流程 No2: ViewRoot对应于ViewRootImpl类,它是连接Wind ...
- 《Android开发艺术探索》读书笔记 (9) 第9章 四大组件的工作过程
第9章 四大组件的工作过程 9.1 四大组件的运行状态 (1)四大组件中只有BroadcastReceiver既可以在AndroidManifest文件中注册,也可以在代码中注册,其他三个组件都必须在 ...
- 《Android开发艺术探索》读书笔记 (4) 第4章 View的工作原理
本节和<Android群英传>中的第3章Android控件架构与自定义控件详解有关系,建议先阅读该章的总结 第4章 View的工作原理 4.1 初始ViewRoot和DecorView ( ...
- 《Android开发艺术探索》读书笔记 (3) 第3章 View的事件体系
本节和<Android群英传>中的第五章Scroll分析有关系,建议先阅读该章的总结 第3章 View的事件体系 3.1 View基本知识 (1)view的层次结构:ViewGroup也是 ...
- Android开发艺术探索(三)——View的事件体系
一.View基础知识 主要介绍内容有:View的位置参数.MotionEvent和TouchSlope对象.VelocityTracker.GestureDetector和Scroller对象 1.什 ...
- android开发艺术探索读书笔记之-------view的事件分发机制
View的点击事件的分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,而这个过程就是分发过程. 分发过程主要由以下 ...
随机推荐
- 找不到"javax.servlet.annotation.WebServlet"解决方法
以前创建的一个项目,打开的时候总是报错. import javax.servlet.annotation.WebServlet; 后来想起当时这个项目是发布在tomcat7.0下面的, 也就是说当时这 ...
- 深入探索 JUnit 4
开始之前 关于本教程 引入 Java 5 注释为 JUnit 带来了显著改变,使它从一个受广大开发人员了解和喜爱的测试框架转变成了一个更为精简但却不那么为人熟知的框架.在本教程中,我将探讨 JUnit ...
- 使用 Golang 编写链代码 (v0.6 )
https://www.ibm.com/developerworks/cn/cloud/library/cl-ibm-blockchain-chaincode-testing-using-golang ...
- Docker配置国内加速器加速镜像下载的方法
在搭建hyperledger fabric的开发环境的时候,用docker去下载镜像,好慢好慢,慢到下了一个下午没有下载完成,最后还是失败了.最后去网上找了一下,发现有配置国内加速器加速镜像下载的方法 ...
- Guava之ImmutableMap使用示例
ImmutableMap 的作用就是:可以让java代码也能够创建一个对象常量映射,来保存一些常量映射的键值对. 分析以下情景,来具体讨论这个的好处. 假设现在有需求如下:根据数据库存的某个key字段 ...
- (十一) 整合spring cloud云架构 - SSO单点登录之OAuth2.0登录流程(2)
上一篇是站在巨人的肩膀上去研究OAuth2.0,也是为了快速帮助大家认识OAuth2.0,闲话少说,我根据框架中OAuth2.0的使用总结,画了一个简单的流程图(根据用户名+密码实现OAuth2.0的 ...
- Ios开发之多线程编程——NSThread
IOS程序在运行的时候是通过主线程来进行UI视图的更新和响应屏幕触摸事件,但是,在视图更新的时候,会有一些非常耗时的工作,这样我们会出现系统出现卡顿的现象,这是因为主线程堵塞造成的,这样会使用户体验非 ...
- 大数据开发实战:Stream SQL实时开发二
1.介绍 本节主要利用Stream SQL进行实时开发实战,回顾Beam的API和Hadoop MapReduce的API,会发现Google将实际业务对数据的各种操作进行了抽象,多变的数据需求抽象为 ...
- 【Kafka】Kafka-分区数-备份数-如何设置-怎么确定-怎么修改
Kafka-分区数-备份数-如何设置-怎么确定-怎么修改 kafka partition 数量 更新_百度搜索 kafka重新分配partition - - CSDN博客 如何为Kafka集群选择合适 ...
- Topic Model的分类和设计原则
Topic Model的分类和设计原则 http://blog.csdn.net/xianlingmao/article/details/7065318 topic model的介绍性文章已经很多,在 ...