在Android中View一直扮演着一个很重要的角色,它是咱们开发中视觉的呈现,我日常也使用着Android提供的丰富且功能强大的控件,有时候遇到一个很炫酷的自定义View的开源库,咱们也是拿来主义,时间长了你就会发现你只是一个只会使用控件和依赖被人开源库的程序员,这并非一个开发者,因此咱们并不能只知足于使用,咱们要理解它背后的工做原理和流程,这样才能本身作出一个属于本身的控件,一直都说自定View是Android进阶中的一道门槛,当其实自定义View当你理解了它的原理后,你就会发现它也不过如此。本文将从源码的角度探讨View工做的三大流程,对View作进一步的认识。俗话说的好:源码才是最好的老师。html
本文代码基于Android8.0,相关源码位置以下:
frameworks/base/core/java/android/*.java(*表明View, ViewGroup, ViewRootImpl)
frameworks/base/core/java/android/FrameLayout.java
复制代码
提到View,就不得不讲起Window,在Window,WindowManager和WindowManagerService之间的关系文章中讲过,Widnow是View得载体,在ViewRootImpl的setView方法中添加Winodw到WMS以前,会先调用requestLayout绘制整颗View Hierarchy的绘制,以下:java
因此咱们先从requestLayout()中看起,该方法以下:android
//ViewRootImpl.java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查是否在主线程,在子线程绘制UI会抛出异常,见下方
checkThread();
//是否measure和layout布局的开关
mLayoutRequested = true;
//一、准备开始遍历View Hierarchy绘制
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
复制代码
requestLayout()中首先会检查线程的合法性,Android规定必须在主线程中操做UI,那么为何不能在子线程中访问UI呢?这是由于Android的UI控件都不是线程安全的,若是在多线程环境下并发访问控件会致使控件处于不可预测状态。接着咱们来看注释1,调用了ViewRootImpl的scheduleTraversals方法,以下:程序员
//ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {//防止同一帧绘制屡次
mTraversalScheduled = true;
//拦截同步Message,优先处理异步Message
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//一、Choreographer回调,里面执行最终会执行mTraversalRunnable中的绘制任务
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//...
}
}
复制代码
在Android4.1以前Android的UI流畅性不好,因此在Android4.1以后引入了Choreographer机制和Vsync机制用来解决这个问题,Choreographer管理者动画、输入和绘制的时机,Vsync叫Vertical Synchronization(垂直同步)信号,每隔 16ms Choreographer就会收到来自native层的Vsync信号,这时Choreographer就会根据事件类型进行相应的回调操做,Choreographer支持4种事件类型回调:输入(CALLBACK_INPUT)、绘制(CALLBACK_TRAVERSAL)、动画(CALLBACK_ANIMATION)、提交(CALLBACK_COMMIT),并经过postCallback方法在对应须要同步Vsync刷新处进行注册,等待回调,关于这个细节和原理能够看Android图形系统-Choreographer和Android垂直同步和三重缓存,这里咱们并不深究Choreographer机制和Vsync机制,咱们看到注释1中的Choreographer的postCallback方法提交了CALLBACK_TRAVERSAL类型的回调,它对应着mTraversalRunnable绘制操做,而mTraversalRunnable是一个TraversalRunnable类型的绘制任务,最终回调会执行这个任务,mTraversalRunnable的run方法源码以下:canvas
//ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//一、里面会执行performTraversals()
doTraversal();
}
}
复制代码
doTraversal()里面会执行performTraversals方法,点开doTraversal方法看一下,以下:缓存
//ViewRootImpl.java
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除拦截同步Message屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//一、今天的主角,performTraversals()方法
performTraversals();
//...
}
}
复制代码
在doTraversal() 方法里面咱们终于看到咱们熟悉的方法:performTraversals()。安全
performTraversals()它是整个View Hierarchy绘制的起点,它里面会执行View绘制的三大工做流程,咱们先看一下精简版的performTraversals方法,以下:多线程
//ViewRootImpl.java
private void performTraversals() {
//mView是在View与ViewRootImpl创建关联的时候被赋值的,即调用ViewRootImpl的setView方法时,它表明着View Hierarchy的根节点,即根视图
final View host = mView;
//...
WindowManager.LayoutParams lp = mWindowAttributes;
//desiredWindowWidth和desiredWindowHeight分别表明着屏幕的宽度和高度
int desiredWindowWidth;
int desiredWindowHeight;
//...
if (mLayoutRequested) {
final Resources res = mView.getContext().getResources();
//...
//一、这里调用了measureHierarchy方法,里面会调用performMeasure方法,执行View Hierarchy的measure流程,见下方
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
//...
}
//...
if(didLayout){
//二、这里调用了performLayout方法,执行View Hierarchy的layout流程
performLayout(lp, mWidth, mHeight);
//...
}
//...
if (!cancelDraw && !newSurface) {
//...
//三、这里调用了performDraw方法,执行View Hierarchy的draw流程
performDraw();
}
//...
}
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
//...
//1.一、顶级View在调用performMeasure方法以前,会先调用getRootMeasureSpec方法来生成自身宽和高的MeasureSpec
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//1.二、这里调用performMeasure方法,执行View Hierarchy的measure流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
performTraversals方法里面很是复杂,咱们看的时候千万不要深究其中的细节,否则就走火入魔了,咱们找出个总体框架就行,咱们先看注释一、二、3,能够看到依此调用**measureHierarchy() -> performLayout() -> performDraw(),而measureHierarchy()里面最终调用performMeasure(),因此performTraversals()能够看做依此调用了performMeasure() -> performLayout() -> performDraw(),分别对应顶级View的measure、layout和draw流程,**顶级View能够理解为View Hierarchy的根节点,它通常是一个ViewGroup,就像Activity的DecorView同样。并发
ps:app
一、在performTraversals()方法中,performMeasure()可能会执行屡次,而performLayout()和performDraw()最多执行一次。
二、本文讨论的顶级View你能够把它类比成Activity的DecorView,可是它其实就是View树的根结点,DecorView也是Activity中View树的根结点。
接下来咱们就照着performTraversals() 中的总体框架来说解View工做的三大流程。
讲解View的measure流程前,不得不先讲解一下MeasureSpec的含义,MeasureSpec是一个32位的int值,它是View的一个内部类,它的高2位表明着SpecMode,表示测量模式,它的低30位表示SpecSize,表示测量大小,系统经过位运算把SpecMode和SpecSize合二为一组成一个32位int值的MeasureSpec。
下面看一下MeasureSpec的里面组成,以下:
//View.java
public static class MeasureSpec {
//左移位数
private static final int MODE_SHIFT = 30;
//位掩码
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
//表明着三种SpecMode
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
//makeMeasureSpec方法是把SpecMode和SpecSize经过位运算组成一个MeasureSpec并返回
public static int makeMeasureSpec(int size,int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//getMode方法是从给定的MeasureSpec中取出SpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//getSize方法是从给定的MeasureSpec中取出SpecSize
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
复制代码
能够看到MeasureSpec提供了三个工具方法分别用来组合MeasureSpec、从MeasureSpec中取出SpecMode、从MeasureSpec中取出SpecSize,其中SpecMode有三种取值,以下:
除了顶级View,其余View的MeasureSpec都是由父容器的MeasureSpec和自身的LayoutParams共同决定的,LayoutParams就是你平时在编写View的xml属性时那些带有layout_XX前缀开头的布局属性,对于顶级View和在View树中子View的MeasureSpec的生成规则有点不同,见下面分析:
因为顶级View是View树的根结点,因此它没有父容器,因此它的MeasureSpec是由屏幕窗口的尺寸和自身的LayoutParams来共同决定,上面注释1.1咱们讲到顶级View在调用performMeasure方法以前,会先调用ViewRootImpl的getRootMeasureSpec方法来生成自身宽和高的MeasureSpec,咱们来看一下getRootMeasureSpec方法,以下:
//ViewRootImpl.java
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT://若是是MATCH_PARENT,那么就是EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT://若是是WRAP_CONTENT,就是AT_MOST
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default://若是是固定的值,也是EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
复制代码
windowSize就是是传入的desiredWindowWidth或desiredWindowHeight,它表示屏幕的大小,rootDimension就是传入的屏幕窗口的LayoutParams的大小模式,对应咱们平时写的layout_width或layout_height属性,该属性无非就三个值:match_parent、wrap_content和固定的数值,因此从getRootMeasureSpec方法能够看到,顶级View的MeasureSpec的建立规则以下:
其中rootSize表示顶级View大小。
在1中,顶级View的MeasureSpec已经建立好了,这时候就要根据这个MeasureSpec去生成子View的MeasureSpec,子View的MeasureSpec的建立是从ViewGroup的measureChildWithMargins方法开始,以下:
//ViewGroup.java
rotected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
//获得子View的margin
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//一、这里调用了getChildMeasureSpec方法,里面就是建立子View的MeasureSpec,这里建立子View宽的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
//同理,这里建立子View高的MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
//若是子View是一个ViewGroup,递归measure下去
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
上述方法会对子View进行measure,由注释1得知,在调用子View的measure方法前,会先调用getChildMeasureSpec方法得到子View的MeasureSpec,从getChildMeasureSpec方法的参数能够看出,子View的MeasureSpec的建立与父容器的MeasureSpec和子View自己的LayoutParams有关,此外还和View的margin及padding有关,下面咱们来看ViewGroup的getChildMeasureSpec方法,以下:
//ViewGroup.java
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//取出父容器的测量模式specMode
int specMode = MeasureSpec.getMode(spec);
//取出父容器的测量大小specSize
int specSize = MeasureSpec.getSize(spec);
// padding是指父容器中已占用的空间大小,所以子View最大可用大小size == 父容器剩余大小 == 父容器的尺寸减去padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY://若是父容器是EXACTLY
if (childDimension >= 0) {//若是子View的LayoutParams是固定大小
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {//若是子View的LayoutParams是MATCH_PARENT //子View的MeasureSpec为父容器剩余大小 + EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {//若是子View的LayoutParams是WRAP_CONTENT
//子View的MeasureSpec为父容器剩余大小 + AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST://若是父容器是AT_MOST
if (childDimension >= 0) {
//子View的MeasureSpec为子View大小 + EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View的MeasureSpec为父容器剩余大小 + AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子View的MeasureSpec为父容器剩余大小 + AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED://若是父容器是UNSPECIFIED,这个平时开发用不到
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
复制代码
能够看到getChildMeasureSpec方法里面的逻辑仍是很清楚的,首先根据父容器的测量模式specMode分为三大类:**EXACTLY、AT_MOST和UNSPECIFIED,**每一类又和子View的LayoutParams的的三种大小模式:固定大小、MATCH_PARENT和WRAP_CONTENT组合,因此总共有3 X 3 = 9种组合,因此根据getChildMeasureSpec方法能够得出子View的MeasureSpec的建立规则以下:
其中childSize表示子View的大小,parentSize表示父容器剩余大小。
分析完View的MeasureSpec的建立后,咱们继续回到View的measure流程,你们都知道ViewGroup是继承自View的,因此View的measure流程,分为两种状况,一种是View的measure流程,一种是ViewGroup的measure流程,可是无论是View的measure流程仍是ViewGroup的measure流程都是从ViewRootImpl的performMeasure()开始,而且都会先调用View的measure方法,以下:
//ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
//...
//一、调用了View的measure方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
咱们继续看View的measure方法,以下:
//View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//...
//一、调用了onMeasure方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
//...
}
复制代码
能够看到measure方法是一个final方法,说明这个方法不可以被子类重写,这个方法把measure的具体过程交给了onMeasure方法去实现,因此View和ViewGroup的measure流程的差别就从这个onMeasure方法开始,见下面分析。
从上述知道View的measure起点在View的measure方法中,而且View的measure方法会调用View的onMeasure方法,View::measure() -> View::onMeasure(),因此咱们直接看onMeasure方法在View中的实现,以下:
//View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//一、 若是View没有重写onMeasure方法,则会调用setMeasuredDimension方法设置宽高,在设置以前先调用getDefaultSize方法获取默认宽高
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
);
}
复制代码
View中的onMeasure方法的默认实现是先调用getDefaultSize方法获取默认宽高,而后再调用调用setMeasuredDimension方法设置View的宽高,当调用setMeasuredDimension方法设置View的宽高后,就能够经过getMeasureWidth()或getMeasureHeight()得到View测量的宽高,因此咱们先看一下 getDefaultSize()方法是如何获取默认的宽高,该方法源码以下:
//View.java
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//若是specMode是UNSPECIFIED,返回的大小就是传进来的size,而这个size就是经过getSuggestedMinimumWidth()或getSuggestedMinimumHeight()方法得到的
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//若是specMode是AT_MOST或EXACTLY,返回的大小就是MeasureSpec中的specSize
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
复制代码
getDefaultSize方法的逻辑很简单,除了UNSPECIFIED这种模式,其余测量模式都返回MeasureSpec中的specSize,而这个specSize就等于父容器给View测量后的大小,因此咱们能够得出一个结论:直接继承View写自定义控件时须要重写onMeasure方法并设置wrap_content时自定义View自身的大小,这是由于若是自定义View在xml文件写了layout_XX = wrap_content这个属性,那么在建立它的MeasureSpec时,它的specMode就会等于AT_MOST,而从getDefaultSize方法看出,若是specMode是AT_MOST或EXACTLY,它们两个返回的值是同样的,都是MeasureSpec中的specSize,经过上面所讲的子View的MeasureSpec的建立规则可知specSize是等于parentSize即父容器剩余的大小,这样就会形成这个自定义View会填充满整个父容器,效果和match_parent同样,并不按你想象那样的大小。因此之后在自定义View时,若是有wrap_content这个场景,就要重写onMeasure方法,能够参考下面的模板,以下:
//View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
int width, height;
//通过计算,控件所占的宽和高分别对应width和height
// …………
//咱们只须要在View为wrap_content时设置咱们通过计算得出的View的默认宽高width和height便可
//其余模式如EXACTLY,就直接设置父容器给咱们测量出来的宽高便可
setMeasuredDimension(
(measureWidthMode == MeasureSpec.AT_MOST) ? width : measureWidth ,
(measureHeightMode == MeasureSpec.AT_MOST) ? height : measureHeight
);
}
复制代码
讲完了getDefaultSize()中AT_MOST和EXACTLY模式状况,接着讲UNSPECIFIED这种模式的状况,从getDefaultSize方法中能够看出若是specMode是UNSPECIFIED,返回的大小就是传进来的size,而这个size就是经过getSuggestedMinimumWidth()或getSuggestedMinimumHeight()方法得到的,因此咱们以getSuggestedMinimumWidth方法为例子,看一些若是获取在UNSPECIFIED模式下的宽,getSuggestedMinimumHeight()方法同理,getSuggestedMinimumWidth方法源码以下:
//View.java
protected int getSuggestedMinimumWidth() {
//根据View有无背景返回大小,getMinimumWidth()见下方
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//Drawable.java
public int getMinimumWidth() {
//getIntrinsicWidth()返回Drawable的宽,默认返回-1
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
复制代码
mBackground就等于View的背景,即android:background属性,mMinWidth就等于你在View的xml布局中写了“android:minWidth”这个属性,mBackground.getMinimumWidth()就是获取View的背景的宽度,因此咱们得出结论:在UNSPECIFIED模式下,若是View没有设置背景,那么View的宽就等于android:minWidth,若是View设置了背景,那么View的宽就等于View的背景background的宽和android:minWidth的最大值,高度同理。
View的onMeasure方法执行完后,就能够经过getMeasureWidth()或getMeasureHeight()得到View测量的宽高,可是有可能会不许确,由于有时候系统会进行屡次measure,才能肯定最终测量宽高,因此最好是在onLayout方法中去获取View的宽高。
从上述知道ViewGroup的measure起点也在View的measure方法中,而View的measure方法会调用View的onMeasure方法,ViewGroup继承自View,可是它是一个抽象类并无重写View的onMeasure方法,而是由ViewGroup的子类如LinearLayout、FrameLayout等重写onMeasure方法以实现不一样的measure流程,这里以FrameLayout为例,**View::measure() -> FrameLayout::onMeasure() **,咱们来看FrameLayout的onMeasure方法,以下:
//FrameLayout.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取子View的个数
int count = getChildCount();
//...
//遍历全部子View,测量每一个子View的大小
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {//若是子View可见
//一、调用ViewGroup的measureChildWithMargins方法,测量子View的大小
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//子View测量完后,FrameLayout就能够经过View的getMeasuredWidth或getMeasuredHeight得到子View的宽高,从而得出本身的宽高
//根据FrameLayout的叠加特性,它自身的测量宽高就是全部子View宽高中的最大值
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
}
//...
}
复制代码
能够看到与View的onMeasure方法不一样的是,FrameLayout的onMeasure方法是遍历它全部的子View,而后逐个测量子View的大小,这个测量子View是经过注释1的measureChildWithMargins方法来完成,这个方法已经在上面子View的MeasureSpec的建立中讲过一点,measureChildWithMargins方法是在FrameLayout的父类ViewGroup中,以下:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
//省略的这部分在上面已经讲过,主要是建立子View的MeasureSpec(childWidthMeasureSpec, childHeightMeasureSpec)
//...
//一、调用子View的measure方法,叫子View本身测量本身
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
measureChildWithMargins方法中首先会根据父容器传进来的parenXXMeasureSpec来建立子View的childXXMeasureSpec,而后调用子View的measure方法,把测量子View的任务又推给了子View,这个过程又回到了2.1所讲的View的measure流程,就再也不赘述,全部子View测量完后,ViewGroup就能够得出本身的测量宽高。
measure流程是三大流程中最复杂的一个,它的总体流程是:从ViewRootImp的performTraversals()方法进入performMeasure()方法,开始整颗View树的测量流程,在performMeasure方法里面会调用View的measure方法,而后measure方法会调用onMeasure方法,若是是View就直接开始测量,设置View的宽高,若是是ViewGroup,则在onMeasure方法中则会对全部的子View进行measure过程,若是子View是一个ViewGroup,那么继续向下传递,直到全部的View都已测量完成。如图:
measure事后就能够经过getMeasureWidth()或getMeasureHeight()得到View测量的宽高。
前面讲解了View的measure过程,若是你理解了,那么View的布局过程也很容易理解的,和measure类似,View的布局过程是从ViewRootImpl的performLayout()开始的,以下:
//ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
//...
final View host = mView;
//...
//一、调用了顶级View的layout方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//...
}
复制代码
在performLayout中主要调用了顶级View的layout方法,顶级View的实例有多是View也有多是ViewGroup,可是这个layout方法是在View中,它不像measure方法那样,它不是final修饰,因此它能够被重写,而且ViewGroup重写了layout方法,咱们先看一下ViewGroup中的layout方法,以下:
//ViewGroup.java
@Override
public final void layout(int l, int t, int r, int b) {
if (...) {
//...
//一、ViewGroup中的重写的layout方法仍是调用了父类即View的layout方法
super.layout(l, t, r, b);
} else {
//...
}
}
复制代码
能够看到ViewGroup重写的layout方法只是作了一些判断,而后最终仍是仍是调用了父类即View的layout方法,因此咱们直接看View的layout方法便可。
View的layout方法以下:
//View.java
public void layout(int l, int t, int r, int b) {
// 注意传进来的四个参数:
// l 表示子View的左边缘相对于父容器的上边缘的距离
// t 表示子View的上边缘相对于父容器的上边缘的距离
// r 表示子View的右边缘相对于父容器的右边缘的距离
// b 表示子View的下边缘相对于父容器的下边缘的距离
//...
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//一、调用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);
//...
}
//...
}
复制代码
layout方法传进来的l、t、r、b分别表明着View的上下左右四个点的坐标,这个四个点的坐标是相对于它的父容器来讲的,这个layout方法主要干了两件事:
//View.java
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
//...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
//...
}
return changed;
}
复制代码
能够看到,setFrame方法主要把l、t、r、b分别赋值给mLeft、mTop、mBottom、mRight,即更新View的四个顶点的位置,这个四个顶点一旦肯定,那么View在父容器中的位置也就肯定了。
//View.java
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
复制代码
可是在ViewGroup中是一个抽象方法,以下:
//ViewGroup.java
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
复制代码
这是由于onLayout方法主要用途是给父容器肯定子View的位置,因此若是自己就是一个View,就无需实现这个方法,可是若是是ViewGroup,它还要布局子View,因此是ViewGroup的子类就要强制实现这个方法,不一样的ViewGroup具备不一样的布局方式,因此不一样的ViewGroup的onLayout方法的实现就不同,咱们仍是以FrameLayout为例,看一下FrameLayout的onLayout方法的实现,以下:
//FrameLayout.java
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
复制代码
FrameLayout的onLayout方法只调用了layoutChildren方法,该方法以下:
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
//获取padding值
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//遍历全部子View,布局每一个子View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//若是子View可见
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//得到measue流程测量出来的子View的宽高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
//子View的左边缘位置
int childLeft;
//子View的上边缘位置
int childTop;
//下面是获取布局方向
//...
//下面根据布局方向计算出childLeft和childTop
//...
//一、根据上面的计算,就算出了一个子View的左边缘位置childLeft和上边缘位置childTop
//从而根据childLeft和childTop得出子View的右边缘位置childRight = childLeft + width,下边缘位置childButtom = childTop + height
//而后调用子View的layout方法
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
复制代码
能够发现layoutChildren里面过程和onMeasure里面的过程很像,只是注释1中调用的是子View的layout方法而不是measure方法,若是这个子View是一个View,那么layout方法里面就能够经过setFrame方法直接肯定自身的位置,若是这个子View是一个ViewGroup,除了调用setFrame方法肯定自身的位置外,还要重复onLayout方法中肯定子View位置的过程,最后一层一层的往下,直到所有都子View的layout完成。
咱们再来看一下layout的总体流程:从ViewRootImp的performTraversals()方法进入performLayout()方法,开始整颗View树的布局流程,在performLayout方法里面会调用layout方法,咱们发现,View的布局过程其实也可想测量过程那样分为View的layout流程和ViewGroup的layout流程,对于View来讲,执行layout方法时只须要直接肯定自身四个顶点的位置便可,而onLayout方法是一个空实现;对于ViewGroup来讲,执行layout方法时除了要肯定自身的四个顶点的位置外,那么它在onLayout方法中还要对本身全部的子View进行layout,最后一层一层的往下,直到所有都layout完成。以下:
layout事后就能够经过View的getWidth()和getHeight()来获取最终的宽高的,这个两个方法的实现以下:
//View.java
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
复制代码
能够发现就是经过View的四个顶点的差值来获得View的准确宽高。
和上面两步类似,View的绘制从ViewRootImpl的performDraw()开始的,以下:
//ViewRootImpl.java
private void performDraw() {
//...
final boolean fullRedrawNeeded = mFullRedrawNeeded;
//...
//一、调用ViewRootImpl的draw方法
draw(fullRedrawNeeded);
//...
}
复制代码
performDraw()方法中并非先调用View的draw方法,而是先调用ViewRootImpl的draw方法,以下:
//ViewRootImpl.java
private void draw(boolean fullRedrawNeeded) {
//获取surface绘制表面
Surface surface = mSurface;
//...
//若是surface表面须要更新
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//判断是否启用硬件加速,便是否使用GPU绘制
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
//...
//使用GPU绘制
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
}else {
//...
//一、调用drawSoftware方法,使用CPU绘制
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
//...
}
复制代码
在ViewRootImpl的draw方法中首先获取须要绘制的区域,而后判断是否使用GPU进行绘制,使用硬件加速是为提升了Android系统显示和刷新的速度,是在在API 11以后引入GPU加速的支持,关于这部分知识可自行查阅资料,不是本文重点,这里咱们只关心注释1,一般状况下咱们使用的是CPU绘制,也就是调用ViewRootImpl的drawSoftware方法来绘制,ViewRootImpl的drawSoftware()方法以下:
//ViewRootImpl.java
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) {
final Canvas canvas;
//...
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
//一、获取指定区域的Canvas对象,即画布,用于绘制
canvas = mSurface.lockCanvas(dirty);
//...
}//省略catch
try {
//...
try {
//...
//二、从View树的根节点开始绘制,触发整颗View树的绘制
mView.draw(canvas);
} finally {
//...
}
} finally {
try {
//三、释放Canvas锁,而后通知SurfaceFlinger更新这块区域
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
//...
}
}
return true;
}
复制代码
drawSoftware方法中主要作了3件事:
第1和第3点都是操做Surface的基本流程,咱们主要看第二点即注释2,调用了View的draw方法,它就是一个模板方法,定义了几个固定的绘制步骤,以下:
//View.java
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
/* * 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) */
//一、绘制背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
//...
//二、保存Canvas图层,为fadin作准备
saveCount = canvas.getSaveCount();
//...
//三、 绘制自身内容,setWillNotDraw()能够控制dirtyOpaque这个标志位
if(!dirtyOpaque) onDraw(canvas);
//四、若是是ViewGroup,绘制子View
dispatchDraw(canvas);
//...
//五、若是须要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
//...
//六、绘制装饰,如滚动条
onDrawForeground(canvas);
}
复制代码
你看那英文注释,它已经替咱们把draw方法中的6大步骤写出来了,其中最重要的就是注释3和4,咱们分别来介绍一下:
//ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
//...
for (int i = 0; i < childrenCount; i++) {
//...
//若是子View可见
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
//调用drawChild方法,见下面
more |= drawChild(canvas, child, drawingTime);
}
}
}
//ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
//仍是调用了View的draw方法
return child.draw(canvas, this, drawingTime);
}
复制代码
能够看到,dispatchDraw方法把绘制子View的任务经过drawChild方法分发给它的子View,若是是一个ViewGroup,又会重复dispatchDraw()过程。
//View.java
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
复制代码
可是若是你不须要绘制任何内容,你能够经过View的setWillNotDraw(true)方法关闭绘制,在默认状况下,View没有启用这个优化标志位,可是ViewGroup会启用,因此当你的自定义ViewGroup须要经过onDraw来绘制内容时,须要显式的打开这个开关setWillNotDraw(false),当你的自定义View不须要onDraw来绘制内容时,须要显式的关闭这个开关setWillNotDraw(true)。
到这里,咱们走完了View的绘制过程,咱们再来看一下draw的总体流程:从ViewRootImp的performTraversals()方法进入performDraw()方法,开始整颗View树的绘制流程,在performDraw()方法中通过层层调用:ViewRootImpl :: draw() -> ViewRootImpl :: drawSoftware() -> View :: draw(),来到View的draw()方法,它里面定义了View绘制的6大步骤,其中对于View来讲,直接调用onDraw()方法绘制自身,对于ViewGroup来讲,还要经过dispatchDraw()把绘制子View的流程分发下去,一层层传递,直到全部View都绘制完毕。如图:
咱们一直讲View的工做原理,但有没有发现ViewRootImpl也出现的很频繁,它虽然不是一个View,但它是链接View和Window之间的纽带,View三大工做流程的起点就是ViewRootImpl的performTraversals()方法,performTraversals()中依此调用了performMeasure() -> performLayout() -> performDraw(),分别对应顶级View的measure、layout和draw流程,而后顶级View的measure流程和layout流程又会分别调用咱们熟悉的onMeasure()、onLayout()方法 ,draw流程有点特别,它是经过dispatchDraw()方法来进行draw流程的传递, 而onDraw()方法只是单纯的绘制自身内容,在onMeasure()方法中会对全部child进行measure过程,同理onLayout()方法中会对全部child进行layout过程,dispatchDraw()方法中会对全部child进行draw过程,如此递归直到完成整颗View Hierarchy的遍历。
该过程如图:
在阅读Android源码时,若是你只是在追踪方法的调用链,这种过程是毫无心义的,可是若是你在这个阅读过程加入了本身的思考,把它的知识点用本身的语言整理,这样才会有所收获。以上就是我对View的工做原理的理解,但愿你们有所收获。
参考资料:
《Android开发艺术探索》