View框架浅析
Posted 劲火星空
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了View框架浅析相关的知识,希望对你有一定的参考价值。
参考文章如下,这几篇文章很好,图文并茂,我这里只是取了一些原文的概念放到这里方便复习:
http://www.jianshu.com/p/a3014f8442b0
整体View结构
控件主要分为两类,一类是View,一类是ViewGroup
如下是View的一些原理
- 所有的View都是矩形的
- View是不能添加子View的,ViewGroup可以
- Activity之所以能加载并且控制View,是因为它包含了一个Window,所有的图形化界面都是由View显示的而Service之所以称之为没有界面的activity是因为它不包含有Window,不能够加载View;
- 一个View有且只能有一个父View;
- 在android中Window对象通常由PhoneWindow来实现的,PhoneWindow将一个DecorView设置为整个应用窗口的根View,即DecorView为整个Window界面的最顶层View。
- DecorView是FrameLayout的子类,它继承了FrameLayout,即顶层的FrameLayout的实现类是Decorview,它是在phoneWindow里面创建的;
- 顶层的FrameLayout的父view是Handler,Handler的作用除了线程之间的通讯以外,还可以跟WindowManagerService进行通讯;windowManagerService是后台的一个服务,它控制并且管理者屏幕;
- 一个应用可以有很多个window,其由windowManager来管理,而windowManager又由windowManagerService来管理;
Measure测量一个View的大小
- 1、MeasureSpe描述了父View对子View大小的期望。里面包含了测量模式和大小。
- 2、MeasureSpe类把测量模式和大小组合到一个32位的int型的数值中,其中高2位表示模式,低30位表示大小而在计算中使用位运算的原因是为了提高并优化效率。
- 3、Android中提供了三种测量模式,EXACTLY 精确模式,AT_MOST最大值模式,UNSPECIFIED不确定模式。默认的是去定的模式。
- 4、系统最终会调用setMeasureDimension(int measuredWidth, int measuredHeight)方法将测量后的宽高设置进去,从而完成测量工作。
- 5、当我们需要自定义的时候,需要自定义的measureWidth()方法和measureHeight()方法对宽高进行了重新定义。从MeasureSpec对象中获取到测量模式和测量大小值,通过判断测量模式,返回不同的测量值。
Layout摆放一个View的位置
- 1、首先是layout函数, Layout方法中接受四个参数,是由父View提供,指定了子View在父View中的左、上、右、下的位置。父View在指定子View的位置时通常会根据子View在measure中测量的大小来决定。
- 2、子View的位置通常还受有其他属性左右,例如父View的orientation,gravity,自身的margin等等,特别是RelativeLayout,影响布局的因素非常多。
- 3、layout和onLayout之间有一个setFrame方法。setFrame方法是一个隐藏方法,所以作为应用层程序员来说,无法重写该方法。该方法体内部通过比对本次的l、t、r、b四个值与上次是否相同来判断自身的位置和大小是否发生了改变。如果发生了改变,将会调用invalidate请求重绘。
- 4、onLayout是ViewGroup用来决定子View摆放位置的,各种布局的差异都在该方法中得到了体现。
Draw画出View的显示内容
View的绘制也是遵循一定的顺序:
- 1、画背景
- 2、画边缘
- 3、画自身: ondraw方法
- 4、画子View: dispatchDraw方法
- 5、画滚动条
draw()->onDraw()->dispatchDraw()
- 1、draw是由ViewRoot的performTraversals方法发起,它将调用DecorView的draw方法,并把成员变量canvas传给给draw方法。而在后面draw遍历中,传递的都是同一个canvas。所以android的绘制是同一个window中的所有View都绘制在同一个画布上。
- 2、onDraw()方法的使用,因为我们的目的就是自定义View,所以当我们测量好了一个View之后,我们就可以间的重写onDraw()这个方法,并在Canvas对象上来绘制所需要的图形。在onDraw()中就有一个参数,该参数就是Canvas canvas对象,使用这个对象即可进行绘图操作。
- 3、之所以要传入一个bitmap,是因为传进来的bitmap与通过这个bitmap创建的Canvas画布是紧紧联系在一起的,这个过程称之为装载画布。
- 4、dispatchDraw
先根据自身的padding剪裁画布,所有的子View都将在画布剪裁后的区域绘制。遍历所有子View,调用子View的computeScroll对子View的滚动值进行计算。根据滚动值和子View在父View中的坐标进行画布原点坐标的移动,根据子在父View中的坐标计算出子View的视图大小,然后对画布进行剪裁。
View框架的measure机制
1.mesure干了什么
Android中View有自使用的机制,把各种尺寸值,经过计算,得到具体的像素值。measure过程会遍历整棵View树,然后依次测量每个View真实的尺寸。具体是每个ViewGroup会向它内部的每个子View发送measure命令,然后由具体子View的onMeasure()来测量自己的尺寸。最后测量的结果保存在View的mMeasuredWidth和mMeasuredHeight中,保存的数据单位是像素。
2.如何合理的测量一颗View树
一个View需要把它内部的match_parent或者wrap_content转换成具体的像素值。
在measure过程中,ViewGroup会根据自己当前的状况,结合子View的尺寸数据,进行一个综合评定,然后把相关信息告诉子View,然后子View在onMeasure自己的时候,一边需要考虑到自己的content大小,一边还要考虑的父布局的限制信息,然后综合评定,测量出一个最优的结果。
3.ViewGroup是如何向子View传递限制信息
谈到传递限制信息,那就是MeasureSpec类了,该类贯穿于整个measure过程,用来传递父布局对子View尺寸测量的约束信息。简单来说,该类就保存两类数据。
1、子View当前所在父布局的具体尺寸。
2、父布局对子View的限制类型。
还是包括那三种类型精确、最大和适应
4.源代码的分析
我们知道,整棵View树的根节点是DecorView,它是一个FrameLayout,所以它是一个ViewGroup,所以整棵View树的测量是从一个ViewGroup对象的measure方法开始的。
View的测量过程
measure->onMeasure->setMeasuredDimension
(1)measure
该方法会调用onMeasure()方法,所以只有onMeasure能被也必须要被override。public final void measure(int widthMeasureSpec, int heightMeasureSpec);
父布局会在自己的onMeasure方法中,调用child.measure ,这就把measure过程转移到了子View中。
(2)onMeasure
具体测量过程,测量view和它的内容,来决定测量的宽高(mMeasuredWidth mMeasuredHeight )。该方法中必须要调用setMeasuredDimension(int, int)来保存该view测量的宽高。
(3)setMeasuredDimension
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight);
当View测量结束后,把测量结果保存起来,具体保存在mMeasuredWidth和mMeasuredHeight中。
ViewGroup的测量过程
measureChildren->measureChild->measureChildWithMargins->getChildMeasureSpec
(1)measureChildren
让所有子view测量自己的尺寸,需要考虑当前ViewGroup的MeasureSpec和Padding。跳过状态为gone的子view
(2)measureChild
测量单个View,需要考虑当前ViewGroup的MeasureSpec和Padding。
(3)measureChildWithMargins
测量单个View,需要考虑当前ViewGroup的MeasureSpec和Padding、margins。
(4)getChildMeasureSpec
measureChildren过程中最困难的一部分,为child计算MeasureSpec。该方法为每个child的每个维度(宽、高)计算正确的MeasureSpec。目标就是把当前viewgroup的MeasureSpec和child的LayoutParams结合起来,生成最合理的结果。
getChildMeasureSpec的过程分析
根据当前自身的状况,以及特定子View的尺寸参数,为特定子View计算一个合理的限制信息
- 首先判断限定信息的模式
- 如果是精确的模式,如果子容器申请的是固定尺寸,就用这个固定尺寸,如果子容器希望和父容器一样大,就使用父容器的尺寸,还有就杀包裹内容啦。
- 最大尺寸模式
- 当前容器尺寸不限定模式
自定义View控件
需要覆写onMeasure来正确测量自己。最后都需要调用setMeasuredDimension来保存测量结果
一般来说,自定义View的measure过程伪代码为:
- 首先根据measureSpc来获得mode和size
- 根据不同的模式来设置不同的值,如果当前是精确模式直接设置大小即可,如果是最大值模式,就设置为内容尺寸和父布局尺寸的最小值。
如果是不限定模式,那么当前值有多大就设置为多大。 - 然后通过setMeasureDimension(viewSize)来设置
自定义ViewGroup控件
不但需要覆写onMeasure来正确测量自己,可能还要覆写一系列measureChild方法,来正确的测量子view,比如ScrollView。或者干脆放弃父类实现的measureChild规则,自己重新实现一套测量子view的规则,比如RelativeLayout。最后都需要调用setMeasuredDimension来保存测量结果。
- ViewGroup开始测量自己的尺寸
- ViewGroup为每个Child计算限定信息
- 将上一步的限定信息传递给子View,然后子View需要measure自己的尺寸
- 子View测量完成后,ViewGroup就可以获取每个子View测量后的尺寸
- ViewGroup会根据自己的状况计算自己的尺寸
- ViewGroup保存自己的尺寸
View框架的layout机制
什么是layout过程
就是给View找到合适的位置
该位置是View相对于父布局坐标系的相对位置,而不是以屏幕坐标系为准的绝对位置。这样更容易保持树型结构的递归性和内部自治性。而View的位置,可以无限大,超出当前ViewGroup的可视范围,这也是通过改变View位置而实现滑动效果的原理。
layout过程干了什么
由于View是以树结构进行存储,所以典型的数据操作就是递归操作,所以,View框架中,采用了内部自治的layout过程。
每个叶子节点根据父节点传递过来的位置信息,设置自己的位置数据,每个非叶子节点,除了负责根据父节点传递过来的位置信息,设置自己的位置数据外(如果有父节点的话),还需要根据自己内部的layout规则(比如垂直排布等),计算出每一个子节点的位置信息,然后向子节点传递layout过程。
对于ViewGroup,除了根据自己的parent传递的位置信息,来设置自己的位置之外,还需要根据自己的layout规则,为每一个子View计算出准确的位置(相对于子View的父布局的位置)。
View对象的位置信息,在内部是以4个成员变量的保存的,分别是mLeft、mRight、mTop、mBottom。他们的含义如图所示。
源代码
protected void onLayout(boolean changed, int left, int top, int right, int bottom);
ViewGroup中,只需要覆写onLayout方法,来计算出每一个子View的位置,并且把layout流程传递给子View。
- 在onLayout中首先遍历子View
- 然后在遍历的for循环中计算每一个子View的位置信息
- 计算的规则包括当前布局规则/子View 的测量尺寸/子View所在的位置索引
- 通过child.layout来设置上面的位置信息
结论
一般来说,自定义View,如果该View不包含子View,类似于TextView这种的,是不需要覆写onLayout方法的。而含有子View的,比如LinearLayout这种,就需要根据自己的布局规则,来计算每一个子View的位置。
View框架的draw
什么是layout过程
View框架中,draw过程主要是绘制View的外观。ViewGroup除了负责绘制自己之外,还需要负责绘制所有的子View。而不含子View的View对象,就负责绘制自己就可以了。
draw过程的主要流程
- 绘制 backgroud(drawBackground)
- 如果需要的话,保存canvas的layer,来准备fading(不是必要的步骤)
- 绘制view的content(onDraw方法)
- 绘制children(dispatchDraw方法)
- 如果需要的话,绘制fading edges,然后还原layer(不是必要的步骤)
- 绘制装饰器、比如scrollBar(onDrawForeground)
源代码
我们知道,整棵View树的根节点是DecorView,它是一个FrameLayout,所以它是一个ViewGroup,所以整棵View树的测量是从一个ViewGroup对象的draw方法开始的。
下面是draw方法的流程
- 绘制background
- 绘制View的content
- 绘制children(dispatchView)
- 绘制装饰器
以上是关于View框架浅析的主要内容,如果未能解决你的问题,请参考以下文章