您的位置:首页 > 其它

深入理解View知识系列四-View的测量规则以及三大方法流程

2018-02-07 10:54 686 查看


通过前面几篇的深入分析,相信大家对View的理解已经很深了,我们说了setContentView背后做了什么,说了View从xml加载到通过WindowManager添加View后的一系列操作,说了Android的事件由来,Canvas的由来等等,这一篇我们将来分析View绘制的三大方法,即measure、layout、draw的工作过程以及一些相关参数的产生规则

深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列二- View底层工作原理以及View的绘制流程

深入理解View知识系列三-Window机制、Canvas的由来、Android事件的由来

深入理解View知识系列四-View的测量规则以及三大方法流程


本篇你会学到什么

什么时候需要重新onMeasure

View的测量规则

三大方法的流程

一些相关参数是如何产生的


上一篇回顾

上一篇我们只要说了一堆WindowXXX都是干什么的,之前有什么区别和联系,因为涉及了太多,所以详细的请看上一篇

还说了Window的类型和级别,简单说Window包括三种类型,分别是:应用Window、子Window和系统Window,应用Window的级别对应1-99,子Window的级别对应1000-1999,系统Window对应2000-2999,级别越高的会覆盖在级别底Window上方,在WMS添加Window时对各种Window进行了检查,如果是系统Window则需要申请权限;如果是子Window,要求父Window必须存在;在7.1以后还专门对Toast类型的Window做了限制

我们还说了Surface是什么,它在ViewRootImpl中被创建,但是这只是一个空壳而已,是在绘制之前通过relayoutWindow方法去native层真正的创建Surface并填充到ViewRootImpl中的Surface里

还说了Canvas是怎么产生的,它是通过Surface被创建的,也就是说其实真正的画布应该是Surface

还有Android的事件是在哪里开发分发的,在ViewRootImpl中的setView方法中,创建了一个事件通道,并在添加Window的时候将这个通道传递到WMS中,WMS中也会创建一个通道并建立联系,然后WMS会通过InputManagerService将通道注册到native层,接着ViewRootImpl创建了一个用来接收事件的监听类,这个类叫WindowInputEventReceiver,所有的事件都是从这个类中开始分发,在分发的过程中会判断是按键事件还是触摸事件。


本编源码基于 7.1.1


测量规则

其实在View的三大方法中,最复杂的就是measure的过程,因为在这个过程中设计了多种模式的测量规则,下面进入正文,通过之前我们分析,我们知道View的真正绘制起始点是在ViewRootImpl中的performTraversals方法中,在这里会相应的调用performMeasure、performLayout、performDraw来分别对应View中的三大方法,在这三个方法中分别会调用View的measure、layout、draw方法,以measure为例,measure中会调用onMeasure方法,在onMeasure中会会所有的子元素进行measure过程,而所有的子元素又会重复这一操作,直到整个View树遍历完成,layout和draw的过程和measure是一样的,只不过他们中间执行的是onLayout和onDraw


理解MeasureSpec

这个类应该不会太陌生,它是View的静态内部类,从字面翻译是测量规格,不过确实也是这样。在重写一个View的onMeasure的时候,会给我们提供两个int参数,他们便是MeasureSpec,一个代表宽度的测量规格,一个代表高度的测量规格,这里说的MeasureSpec是通过MeasureSpec类生成的int值并不是MeasureSpec这个类。这个值是由父View传递到子View中的,MeasureSpec这个类通过计算后得出的int值是包装了View测量模式和尺寸的,这个得出的值一个32位的整型数,高两位代表了测量模式,后30位代表了测量尺寸,View的测量的模式分为3种,分别对应了UNSPECIFIED、EXACTLY、AT_MOST,我们可以通过这个int值获取到当前View的测量模式和尺寸。下面看一下这个类

MeasureSpec值的产生有两种情况,一种是顶层View,对于顶层的DecorView计算MeasureSpec值是通过屏幕的宽高和自身的LayoutParams来决定的,另一种普通View是通过父View的MeasureSpec值和View本身的LayoutParams来决定的

public static class MeasureSpec {
//用来标记需要位计算需要位数
private static final int MODE_SHIFT = 30;
//可以理解为用来计算的key
//0x3是16进制,对应的二进制是11,左移30位变成了
//1100 0000 0000 0000 0000 0000 0000 0000,最左端的数字代表符号位,0代表正数,1代表1负数
// 那么最终结果计算的10进制结果是 -1073741824
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
//无约束模式:计算的最终值是0
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//精确模式:
//1左移30位后的值 0100 0000 0000 0000 0000 0000 0000
//转换成10进制是 1073741824
public static final int EXACTLY     = 1 << MODE_SHIFT;
//最大模式:
//2左移30位后 1000 0000 0000 0000 0000 0000 0000
//转换成10进制是 -2147483648
public static final int AT_MOST     = 2 << MODE_SHIFT;
//用来计算包装尺寸和测量模式的方法
//我们可以看到这里对传入的size做了限制,只能是0 - (1 << MeasureSpec.MODE_SHIFT) - 1)的值
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
//调用了上面的makeMeasureSpec
return makeMeasureSpec(size, mode);
}
//从measurespec值中获取测量模式
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
//从measurespec值中获取尺寸
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
....
}


MeasureSpec这个类算上注释一共130多行代码,最主要的就上面几个常量和方法,可以看到MeasureSpec声明的几个常量,有一个用来计算的Key,还有三个测量模式,在注释中也给出了计算后的值,而且可以看到用来计算makeMeasureSpec的方法限制了传入的尺寸大小,只能是0 - (1 << MeasureSpec.MODE_SHIFT)
- 1)的值,那么也就是说这个View的最大尺寸也就只能是它,因为需要将高两位让出来作为测量模式,那么后30位最大也就是全是1,也就是0011 1111 1111 1111 1111 1111 1111 1111,这个数换算后的值为1073741823。

三种测量模式:

UNSPECIFIED : 无约束模式:该模式下,父View不会施加任何的约束,也就是说想要多大给多大,一般情况下我们很少用到

EXACTLY : 精确模式:该模式下,一般对应于View的精确尺寸 或者 MATCH_PARENT,为什么说一般呢?这是因为还需要看父View是什么测量模式

AT_MOST : 最大模式:该模式下,子View可获得最大尺寸是父View的可用大小,一般对应于 WRAP_CONTENT


DecorView的测量规则

MeasureSpec值是通过父View传递过来的,而我们在之前的分析中说过,所有的View全部都存在ViewParent,哪怕是DecorView,它的Parent是ViewRootImpl,那么下面我们就先来看看ViewRootImpl对DecorView的测量规则,在ViewRootImpl中调用performMeasure之前会先计算出宽高MeasureSpec值。

源码位置:/frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() {
....
//计算MeasureSpec值
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//测量宽高并传入刚刚计算的MeasureSpec值
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
....
}


我们可以看到宽高的MeasureSpec值都是调用了getRootMeasureSpec方法,而传入的两个值一个是屏幕的尺寸,另一个是Window的LayoutParams中的宽高尺寸,其实也就是DecorView自己的LayoutParams,进去看一下这个方法

源码位置:/frameworks/base/core/java/android/view/ViewRootImpl.java
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
//判断宽高
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
//如果是MATCH_PARENT的情况
//创建measureSpec值,高度指定为屏幕宽高,模式为精确模式
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
//如果是WRAP_CONTENT
//创建measureSpec值,宽度为屏幕宽高,模式为最大模式
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
//如果是其他的情况,宽高为DecorView的LayoutParams的宽高,模式为精确模式
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
//返回measureSpec值
return measureSpec;
}


以上就是DecorView的测量规则,通过上面的代码我们可以看出来,会先判断DecorView自己的LayoutParams宽高尺寸,根据不同情况计算measureSpec值,这里说一下为什么会有这个判断,前面几篇我们介绍过,在DecorView创建的时候首先会获取Window的参数,根据不同的参数选择不同的布局加载,那么不同的布局可能设置的宽高并不一样


View的测量规则

接着上面说,DecorView

我们应该都知道View的测量是调用了measure方法,并且这个方法是final的,也就是说不可以被重写的方法,但是不同的View样式也不同,那么测量的规则和计算逻辑也肯定不一样,所以android提供了onMeasure方法,这个方法会在measure中被调用,我们可以通过重写onMeasure来实现自己的测量规则并通过setMeasuredDimension(int measuredWidth, int measuredHeight)方法确定View的测量宽高。

那么能作为父View的只能是ViewGroup,可以通过查看源码可以看出来,View也确实没有实现ViewParent接口,而ViewGroup却实现了,可是通过查看源码发现ViewGroup是一个抽象类,并且没有重写onMeasure方法,不过想想这是正常的,毕竟每个ViewGroup的特性都不同,是没有办法统一实现的,例如FrameLayout、LinearLayout、RelativeLayout,都有属于自己的特性,所以他们只能根据自己的特性去实现测量规则,虽然ViewGroup没有重写onMeasure方法,但是它却提供了measureChildWithMargins、measureChild方法来来对子View进行测量,这两个方法类似,只不过是一个包含了子View的Margins一个不包含,我们我们看一下measureChildWithMargins。

源码位置:/frameworks/base/core/java/android/view/ViewGroup.java
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//获取子View的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//获取子View的MeasureSpec值
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//递归调用子View的measure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}


我们可以看到子View的MeasureSpec值是通过getChildMeasureSpec方法得到的,传入的三个参数分别为父View的measureSpec值,自身的padding值和子View的margin值相加,子View的尺寸,我们再看看measureChildWithMargins方法

源码位置:/frameworks/base/core/java/android/view/ViewGroup.java
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//  获取父View的测量模式和尺寸
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//计算最大的可用尺寸
int size = Math.max(0, specSize - padding);
//定义最终的尺寸和模式
int resultSize = 0;
int resultMode = 0;
//判断父View的模式
switch (specMode) {
// 如果父View是精确模式
case MeasureSpec.EXACTLY:
//再判断子View自己的尺寸
if (childDimension >= 0) {
//如果大于0,说明是具体的尺寸,那么定义最终的尺寸就是子View声明的尺寸,模式为精确模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 如果是MATCH_PARENT,定义最终的尺寸为父View的最大可用尺寸,模式为精确模式
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 如果是WRAP_CONTENT,定义最终的尺寸为父View的最大可用尺寸,模式为最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 如果父View是最大模式
case MeasureSpec.AT_MOST:
//再判断子View的尺寸
if (childDimension >= 0) {
//如果大于0,说明是具体的尺寸,那么定义最终的尺寸就是子View声明的尺寸,模式为精确模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 如果是MATCH_PARENT,定义最终的尺寸为父View的最大可用尺寸,模式为最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 如果是WRAP_CONTENT,定义最终的尺寸为父View的最大可用尺寸,模式为最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 如果是UNSPECIFIED模式
case MeasureSpec.UNSPECIFIED:
//再判断子View的尺寸
if (childDimension >= 0) {
//如果大于0,说明是具体的尺寸,那么定义最终的尺寸就是子View声明的尺寸,模式为精确模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 如果是MATCH_PARENT,在判断View中sUseZeroUnspecifiedMeasureSpec的值,true则尺寸为0,否则为父View的最大可用尺寸,模式为UNSPECIFIED
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 如果是WRAP_CONTENT,在判断View中sUseZeroUnspecifiedMeasureSpec的值,true则尺寸为0,否则为父View的最大可用尺寸,模式为UNSPECIFIED
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//通过MeasureSpec类生成子View的measureSpec值
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}


getChildMeasureSpec的方法看着很长,其实逻辑很简单,主要就是根据父View的测量模式和子View的尺寸最终生成子View自己的MeasureSpec值,我们可以看出来,父View的测量模式不同,那么计算规则也不同,但是总结一下,就只有如下几种情况,这里我们我们不分析UNSPECIFIED模式,这种模式很少用

不管父View是哪种模式,只要子View的尺寸大于0,那么它最终的尺寸就是子View自己的尺寸,并且模式为精确模式

如果子View的尺寸是MATCH_PARENT,那么就要看父View是哪种模式的,子View的模式和父View相同,并且尺寸为父View的最大可用尺寸(不包括父View为UNSPECIFIED模式时)

如果子View的尺寸是WRAP_CONTENT,那么子View的的模式为AT_MOST最大模式,尺寸为父View的最大可用尺寸(不包括父View为UNSPECIFIED模式时)


measure过程

上面说了需要通过onMeasure方法来完成View的测量,如果只是一个普通View的话通过onMeasure方法就可以确定自己的尺寸了,但是如果是ViewGroup的话还需要在这里对所有的子View进行遍历并执行measure方法,现在我们先来看一下View中的onMeasure方法

源码位置:/frameworks/base/core/java/android/view/View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//直接设置View的宽高尺寸,但是传入的参数是getDefaultSize方法
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}


我们看到View的onMeasure方法直接调用了setMeasuredDimension方法来设置View的宽高,方法中传入的是getDefaultSize方法,我们看看getDefaultSize方法做了什么。

源码位置:/frameworks/base/core/java/android/view/View.java
public static int getDefaultSize(int size, int measureSpec) {
//声明最后需要返回的尺寸为传入的尺寸
int result = size;
//获取父View的模式和尺寸
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//如果是UNSPECIFIED模式的话则返回的是传入的尺寸
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
//其他情况,将尺寸赋值为父View的可用大小尺寸
result = specSize;
break;
}
return result;
}


我们看到了,View设置默认大小的方法中,会判断父View的测量模式,如果是UNSPECIFIED模式,最终的尺寸是传进来的尺寸,否则其他两种情况全部将最终的尺寸指定为父容器的最大可用大小,因为这里的measureSpec值是父View传递过来的。size的值是通过一个是getSuggestedMinimumWidth或者一个是getSuggestedMinimumHeight方法生成的,我们来看看做了什么

源码位置:/frameworks/base/core/java/android/view/View.java
protected int getSuggestedMinimumHeight() {
//判断是否存在背景
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}


上面两个方法都是判断了mBackground是否为空,这个mBackground是Drawable类型,如果不存在背景则直接返回mMinHeight或者mMinWidth,这两个值对应于android:minHeight和android:minWidth两个属性,如果不指定,默认为0。如果mBackground不为空,则会返回View的最小尺寸和Drawable的最小尺寸的最大值,这里需要注意一点ShapeDrawable默认没有原始的宽高。通过View中的onMeasure方法我们可以得出一个结论,直接继承View的控件一般情况下需要重写onMeasure方法,因为View的默认实现中在View设置为wrap_content的时候返回的大小是父View的最大可用尺寸,当然不排除你本身就想要这种效果!


下面我们在看一下ViewGroup的measure过程,View是一个抽象类,它并没有重写onMeasure方法,因为什么上面已经说过了,每种ViewGroup都有自己的特性,无法统一实现,这里我们就拿FrameLayout来分析,因为DecorView就继承了FrameLayout,这样对整个流程会更清晰,DecorView也重写的onMeasure方法,但是在onMeasure中又调用了super.onMeasure方法,最终还是走到FrameLayout中。

源码位置:/frameworks/base/core/java/android/weight/FrameLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取子元素的个数
int count = getChildCount();
//判断父View的宽高测量模式中是否不为精确模式
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//遍历所有的子元素
for (int i = 0; i < count; i++) {
//获取每一个子元素
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//使用ViewGroup提供的测量方法,这个方法我们上面分析过,该方法会调用子元素的measure方法,如果子元素还是ViewGroup会再次重复整个逻辑
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
//计算宽高
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
//父View的宽高测量模式至少有一个不为精确模式
if (measureMatchParentChildren) {
//自己的尺寸至少有一个为MATCH_PARENT则添加到集合中
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// 调整宽高
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// 调整宽高
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 调整宽高
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//确定宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
//如果父View的测量模式至少有一个为不是精确模式,并且子元素的宽高为MATCH_PARENT,则进行重新测量计算
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}


这里我们可以看到,FrameLayout会遍历所有的子元素调用ViewGroup提供的测量子元素的方法,如果被测量的子元素还是ViewGroup则会重复执行measure-onMeasure方法,直到整个View树遍历完成


layout过程

layout的过程主要是ViewGroup用来确定子元素位置的一个过程,ViewGroup重写了View的layout方法,并且加入了final,不允许自雷重写,并且抽象了onLayout方法,继承ViewGroup的子类必须重写onLayout方法,这也更能说明layout过程的作用。当ViewGroup的位置确定以后会在onLayout方法中遍历所有的子元素并调用子元素的layout方法,而View会通过layout方法来确定自己的位置。在ViewRootImpl中会在performLayout方法中调用DecorView的layout方法,而DecorView继承FrameLayout,也就是一个ViewGroup,在ViewGroup的layout方法中又调用了super.layout方法,也就是说我们只需要看View的layout方法就好了

源码位置:/frameworks/base/core/java/android/view/View.java
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//声明view的四个坐标点
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//最终都会调用setFrame方法,setFrame方法确定了View自身的四个坐标点的位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//调用onLayout方法
onLayout(changed, l, t, r, b);
....
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}


我们看到上面首先通过setFrame方法来确定了本身的4个坐标点,接着会调用onLayout方法,当View确定了坐标点后,我们就可以通过getWidth,getHeight方法来获取View的宽高的
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
...
//主要来确定4个坐标点的位置
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
...
return changed;
}
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}


现在我们应该知道View的宽高尺寸必须要在View的layout方法走完了以后才可以获取的到。接着分析View的layout方法,这里假设这就是DecorView的流程,它在ViewRootImpl中被调用了mView.layout方法,这个方法最终会执行到View中,也就是上边的layout方法,那么它首先会通过setFrame方法确定自己的4个坐标点,接着调用自己的onLayout方法,而DecorView又继承FragmentLayout,FrameLayout继承了ViewGroup,是必须重写onLayout的,记下来我们就去看一下FrameLayout中的onLayout方法

源码位置:/frameworks/base/core/java/android/weight/FrameLayout.java
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//逻辑全部封装到了layoutChildren方法中
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
//  FrameLaoyut中onLayout的所有逻辑
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
//获取子元素的个数
final int count = getChildCount();
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
...
//调用子元素的layout方法,如果子元素还是ViewGroup的话会重复以上过程
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}


整个逻辑主要就是为了遍历所有的子元素,然后按照属于自己的特性和规则来调用子元素的layout方法,当子元素还是ViewGroup的话,会继续重复这一过程,知道整个View树全部layout完成


drawa过程

draw过程主要就是将View绘制到屏幕上的一个过程,上次我们也说过了draw方法中的Canvas是通过Surface创建并传递过来的,在ViewRootImpl中会通过performDraw方法开始draw的过程,最后通过drawSoftware方法创建Canvas并调用DecorView的draw方法,来看一下View的draw方法
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
*      1. Draw the background
*      2. If necessary, save the canvas' layers to prepare for fading
*      3. Draw view's content
*      4. Draw children
*      5. If necessary, draw the fading edges and restore layers
*      6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
...
}


通过上面的注释也可以看出来,View的绘制主要分为6步,而且注释中还有一句 skip step 2 & 5 if possible (common case),多数情况下会跳过2和5,我们也只说其他几部

绘制背景

调用onDraw方法,如果有自己的绘制规则的话

调用dispatchDraw方法来绘制所有的子View

绘制装饰(例如滚动条)

这里我们接着说绘制的过程,可以看到draw方法的第4步,调用了dispatchDraw方法,这个方法在View中是一个空实现,但是在ViewGroup中可就不是了,来看看
protected void dispatchDraw(Canvas canvas) {
....
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
//绘制子元素
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
//绘制子元素
more |= drawChild(canvas, child, drawingTime);
}
}
//省略的代码也是一堆绘制子元素的逻辑
....
...
}
//drawChild方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}


其实最终还是要调用子元素的draw方法,当被调用的子元素还是一个ViewGroup的时候还会继续重复这一动作,直到整个View树绘制完成。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息