view绘制相关

1. 绘制顺序

绘制过程从布局的根节点开始,然后对整个布局树型结构(layout tree)进行测量并绘制,绘制过程沿着布局树型结构(layout tree)进行,依次渲染其中的各个View。在这个遍历过程中,遇到ViewGroup时,每个ViewGroup(注意,ViewGroup也属于View的一种)将依次绘制包含于其中的View;遇到View时,View将绘制自身。由于绘制的过程是沿着布局树型结构(layouttree)遍历的,所以,布局树型结构(layout tree)中的父节点将先于孩子结点被绘制,而兄弟结点则按照它们出现的顺序进行绘制。

 

2. pass传递

绘制布局需要经过两个pass process(传递过程?),分别是measure pass和layoutpass(可以翻译为测量的传递过程,计算布局的传递过程?)。measure pass是在方法measure(int,int) 中进行的,measure pass自上而下地递归遍历布局结构。在递归过程中,每个View都沿着布局树传递dimension specifications,在measure pass结束时,每个View都将获得自身的测量结果。第二个pass process是layout pass,这个过程在在layout(int,int,int,int) 中进行,并且measurepass一样是自上而下的(top-down),在layout pass过程中,每个父结点都将利用measurepass的测量结果,计算出各个子结点的位置

 

3.子节点的绘制冲突解决

各个View的测量宽度和测量高度必须遵从其父结点传递过来的限制条件(其实就是上文提到的dimension specifications)。这样才能保证在measure pass结束时,每个父节点能接受其各个子结点的测量结果。一个parent View可能不止触发其子结点的measure()方法依次,比如,父结点可能第一次首先测量每个没指定大小的子结点,测量出它们需要占用多大的空间,而如果经过第一轮测量,各个子结点的计算结果相互之间有所冲突,则父结点将再次调用各个子结点的onMeasure()方法测量各个子结点。

 

4. ViewGroup.LayoutParams和MeasureSpec

 

(1) ViewGroup.LayoutParams

View对象可以借助ViewGroup.LayoutParams对象去告诉它的父结点,它需要怎样的大小和放置在什么位置。最基础的ViewGroup.LayoutParams类只能描述View的高和宽。它可以指定的高和宽有以下三类

某个确切的数值

MATH_PARENT,代表该View希望和它的父结点一样大(减去padding)

WRAP_CONTENT,代表该View希望大小足够包围住它的内容(加上padding)

ViewGroup.LayoutParams有几个子类,它们分别为ViewGroup几个对应的子类而存在。比如,RelativeLayout有自己的一个ViewGroup.LayoutParams的子类,它有一个特殊的功能,可以讲子结点设置在水平方向上的中点,或者垂直方向上的中点。

 

(2) MeasureSpec

MeasureSpec对象是由父结点传递给子节点的一个对象,父结点对子结点的限制信息保存在该对象中。MeasureSpec对象可以有以下三种模式:

UNSPECIFIED:父结点对子结点的大小没有任何要求。

EXACTLY: 父结点要求其子节点的大小指定为某个确切的值。其子节点以及其他子孙结点都需要适应该大小。     

AT MOST:父结点要求其子节点的大小不能超过某个最大值,其子节点以及其他子孙结点的大小都需要小于这个值  

 

requestLayout

当一个VIEW的布局属性发生了变化的时候,可以调用该方法,让父VIEW调用onmeasure 和onlayout重新定位该view的位置,需要在UI线程调用

 

invalidate

强制使view重绘,需要在UI线程调用

 

postinvalidate

作用同上,但是可以直接在子线程调用

 

最高两位是00的时候表示"未指定模式"。即MeasureSpec.UNSPECIFIED

最高两位是01的时候表示"'精确模式"。即MeasureSpec.EXACTLY

最高两位是11的时候表示"最大模式"。即MeasureSpec.AT_MOST

 

典型用法:

1.  private int getMeasuredLength(int length, boolean isWidth) {      

2.      int specMode = MeasureSpec.getMode(length);      

3.      int specSize = MeasureSpec.getSize(length);      

4.      int size;      

5.      int padding = isWidth ? getPaddingLeft() + getPaddingRight()      

6.              : getPaddingTop() + getPaddingBottom();      

7.      if (specMode == MeasureSpec.EXACTLY) {      

8.          size = specSize;      

9.      } else {      

10.         size = isWidth ? padding + mWave.length / 4 : DEFAULT_HEIGHT      

11.                 + padding;      

12.         if (specMode == MeasureSpec.AT_MOST) {      

13.             size = Math.min(size, specSize);      

14.         }      

15.     }      

16.     return size;      

17. }    

5. view的measure过程

具体来说,View的父控件ViewGroup会调用View的measure方法,ViewGroup会将一些宽度和高度的限制条件传递给View的measure方法。

在View的measure方法会首先从成员变量中读取以前缓存过的量算结果,如果能找到该缓存值,那么就基本完事了,如果没有找到缓存值,那么measure方法会执onMeasure回调方法,measure方法会将上述的宽度和高度的限制条件依次传递给onMeasure方法。onMeasure方法会完成具体的量算工作,并将量算的结果通过调用View的setMeasuredDimension方法保存到View的成员变量mMeasuredWidth 和mMeasuredHeight中。

量算完成之后,View的父控件就可以通过调用getMeasuredWidth、getMeasuredState、getMeasuredWidthAndState这三个方法获取View的量算结果。

 

measure

---------------------------------------------->

首先,我们要知道并不是只要View的measure方法执行的时候View就一定要傻傻的真的去做量算工作,View也喜欢偷懒,如果View发现没有必要去量算的话,那它就不会真的去做量算的工作。

具体来说,View先查看是不是要强制量算以及这次measure中传入的MeasureSpec与上次量算的MeasureSpec是否相同,如果不是强制量算或者MeasureSpec与上次的量算的MeasureSpec相同,那么View就不必真的去量算了。

如果不满足上述条件,View就考虑去做量算工作。但是在量算之前,View还想偷懒,它会以MeasureSpec计算出的key值作为键,去成员变量mMeasureCache中查找是否缓存过对应key的量算结果,如果能找到,那么就简单调用一下setMeasuredDimensionRaw方法,将从缓存中读到的量算结果保存到成员变量mMeasuredWidth和mMeasuredHeight中。

如果不能从mMeasureCache中读到缓存过的量算结果,那么这次View就真的不能再偷懒了,只能乖乖地调用onMeasure方法去完成实际的量算工作,并且将尺寸限制条件widthMeasureSpec和heightMeasureSpec传递给onMeasure方法

<=============================

 

onMeasure

------------------------------->

setMeasuredDimension ->getDefaultSize -> getSuggestedMinimumWidth/Height

 

如果没有给View设置背景,那么就返回View本身的最小宽度mMinWidth

如果给View设置了背景,那么就取View本身最小宽度mMinWidth和背景的最小宽度的最大值

那你可能有疑问,View中保存的最小宽度mMinWidth的值是从哪来的呢?实际上有两种办法给View设置最小宽度。

<===================

 

我们在onMeasure方法中发现,onMeasure会执行以下两行代码:getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)

我们已经研究了getSuggestedMinimumWidth/Height,知道其会返回View的最小宽度和高度,现在我们开始研究getDefaultSize方法。Android会将View想要的尺寸以及其父控件对其尺寸限制信息measureSpec传递给getDefaultSize方法,该方法要根据这些综合信息计算最终的量算的尺寸。

 

 

 

 

getDefaultSize

------------------------------------------------------->

首先根据measuredSpec解析出对应的specMode和specSize

当mode是UNSPECIFIED时,View就直接用自己想要的尺寸size作为量算的结果

当mode是AT_MOST或EXACTLY时,View就用其父ViewGroup指定的尺寸作为量算的结果

最终,View会根据measuredSpec限制条件,得到最终的量算的尺寸。

这样在onMeasure方法中,
当执行getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)时,我们就得到了最终量算到的宽度值;
当执行getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)时,我们就得到了最终量算到的高度值。

<==================================

 

全部测量完了, 再把值保存到view的相关成员变量中

setMeasuredDimension

------------------------------------------------------------>

在前面我们研究onMeasure方法时就已经看到setMeasuredDimension会调用getDefaultSize方法,会将已经量算到的宽度值和高度值作为参数传递给setMeasuredDimension方法,

将量算完成的宽度measuredWidth保存到View的成员变量mMeasuredWidth中

将量算完成的高度measuredHeight保存到View的成员变量mMeasuredHeight中

最后将View的状态位mPrivateFlags设置为已量算状态

<====================================

 

6.viewgroup的读取量算结果的

为此,View提供了三组方法,分别是:
1. getMeasuredWidth和getMeasuredHeight方法:  返回测量的结果
2. getMeasuredWidthAndState和getMeasuredHeightAndState方法: 返回测量结果和状态
3. getMeasuredState方法: 返回测量状态

 

子view返回的mMeasuredWidth

-------------------------------------------------------->

其高位的第一个字节为第一部分,用于标记量算完的尺寸是不是达到了View想要的宽度,我们称该信息为量算的state信息。其低位的三个字节为第二部分,用于存储实际的量算到的宽度。

由此我们可以看出Android真是物尽其用,一个变量能包含两个信息,这个有点类似于measureSpec的道理,但是二者又有不同:

measureSpec是将限制条件mode从ViewGroup传递给其子View。mMeasuredWidth、mMeasuredHeight是将带有量算结果的state标志位信息从View传递给其父ViewGroup。

<==================================

 

resolveSizeAndState

-------------------------------------------------------->

子view向父view表达自己对父view限制的不满

当specMode为AT_MOST,并且父控件指定的尺寸specSize小于View自己想要的尺寸时,我们就会用掩码MEASURED_STATE_TOO_SMALL向量算结果加入尺寸太小的标记,这样其父ViewGroup就可以通过该标记其给子View的尺寸太小了,然后可能分配更大一点的尺寸给子View。

getDefaultSize方法只是onMeasure方法中获取最终尺寸的默认实现,其返回的信息比resolveSizeAndState要少,那么什么时候才会调用resolveSizeAndState方法呢?主要有两种情况:

Android中的许多layout类都调用了resolveSizeAndState方法,比如LinearLayout在量算过程中会调用resolveSizeAndState方法而非getDefaultSize方法。 我们自己在实现自定义的View或ViewGroup时,我们可以重写onMeasure方法,并在该方法内调用resolveSizeAndState方法。

<======================================

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值