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)
  • 绘制装饰器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值