1 初识ViewRoot和DecorView
1.1 DecorView
有分析到Activity中界面加载显示的基本流程原理,最终分析结果就是下面的关系,id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现。
1.2 ViewRootImpl对象和DecorView建立联系
ViewRoot对应于ViewRootImpl类,他是连接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot来完成的。在ActivityThread中,当Activity被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立联系,这个可以参照源码:
(1)WindowManagerImpl.addView()
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
(2)WindowManagerGlobal.addView()
// 创建ViewRootImpl对象
ViewRoot root = new ViewRootImpl(view.getContext(), display);
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
try {
// 将ViewRootImpl对象和DecorView建立联系,view是DecorView
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
throw e;
}
}
(3)ViewRootImpl的setView(…) 方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
// 将成员变量mAdded的值设置为true,表示当前正在处理的一个ViewRoot对象已经关联好一个View对象了
mAdded = true;
// 调用ViewRootImpl类的另外一个成员函数requestLayout()来请求对应用程序窗口视图的UI作第一次布局.
requestLayout();
// 调用ViewRootImpl类的静态成员变量sWindowSession所描述的一个类型为Session的Binder代理对象的成员函数add来请求WindowManagerService增加一个WindowState对象,以便用来描述当前正在处理的一个ViewRoot所关联的一个应用程序窗口
...
}
}
}
(4)ViewRootImpl.requestLayout(…)方法
@Override
public void requestLayout() {
// 没有在处理LayoutInLayout请求时
if (!mHandlingLayoutInLayoutRequest) {
// 做个安全检查:当请求重新布局的线程和生成View树在同一个线程时
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
(5)ViewRootImpl.scheduleTraversals(…)方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
这里会post一个runnable请求就是mTraversalRunnable,
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
(5)进入ViewRootImpl.doTraversal()方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// Android View的起点:performTraversals方法
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
1.3 Android View的起点:ViewRootImpl的performTraversals()
(1)ViewRootImpl.performTraversals()方法
// ViewRootImpl.java
private void performTraversals() {
// 1.处理mAttachInfo的初始化,并根据resize、visibility改变的情况,给相应的变量赋值。
final View host = mView;
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
int desiredWindowHeight;
...
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
// 确认绘制需求
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
// 确认window的size是否有改变
windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
}
...
// Activity处于停止
if (!mStopped || mReportNextDraw) {
// 判断是否需要重新测量
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()
|| contentInsetsChanged || updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 执行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
...
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
// 执行布局
performLayout(lp, mWidth, mHeight);
}
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
// 执行绘制
performDraw();
}
}
(2)整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法开始的:
(在上一篇博客----Android系统分析之Window的视图对象的创建过程分析-的最后阶段分析到)。
(3)performTraversals内部会调用performMeasure()、performLayout()、performDraw(),完成完整的测量、布局、绘制流程。但并不表示每次都会走完三个流程,layoutRequested就表示是否measure和layout。ViewRootImpl负责管理View链,最顶层的是DecorView,是一个FrameLayout,measure等流程都是从上而下进行的,也是从DecorView开始。
1.4 performTraversals()的步骤
在perfromMeasure中会调用measure()方法,在measure()方法中又调用onMeasure(),这个时候measure流程就从父容器传递到子元素了,这样就完成了一次measure过程,接着子元素会重复父容器的measure过程,如此反复的完成了整个View树的遍历。同理,其他两个也是如此,唯一有点区别的是perfromDraw()的传递过程是在draw反复中通过dispatchDraw()来实现的,不过这并没有什么本质的区别。
(1)measure过程决定了View的宽高,Measure完成之后可以通过getMeasureWidth()和getMeasureHeight()来获取View测量后的高宽,在所有的情况下几乎都是等于最终的宽。
(2)layout过程决定了view的四个顶点的坐标和实际View的宽高,完成之后,通过getTop(),getLeft(),getRight(),getBottom()获得。
(3)Draw决定了View的显示,只有draw()方法完成了之后,view才会显示在屏幕上。
2 理解MeasureSpec
MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说很大程度上是因为这个过程还收到了父容器的影响,因为父容器影响MeasureSpec的创建过程,在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽高。
2.1 MeasureSpec
(1)源码
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 00左移30位,00 0000000000000000000000000000
public static final int EXACTLY = 1 << MODE_SHIFT; // 01左移30位,01 0000000000000000000000000000
public static final int AT_MOST = 2 << MODE_SHIFT; // 10左移30位,10 0000000000000000000000000000
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);
}
}
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
(2)分析
MeasureSpec代表一个32位int值,高两位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某个测量模式下的规格大小,如图:
public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 00左移30位,00 0000000000000000000000000000
public static final int EXACTLY = 1 << MODE_SHIFT; // 01左移30位,01 0000000000000000000000000000
public static final int AT_MOST = 2 << MODE_SHIFT; // 10左移30位,10 0000000000000000000000000000
(3)含义
MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,SpecMode和specSize也是一个int值,一直SpecMode和specSize可以打包成一个MeasureSpec。SpecMode有三类,每一类都有特殊的含义:
(1)UNSPECIFIED(最高两位00,表示未指定模式):父容器不对View有任何限制,当前控件可以随便使用空间,一般用于系统内部组件。
(2)EXACTLY(最高两位01,表示精确模式):将控件的layout_width或layout_height指定为match_parent,或者为具体数值时如andorid:layout_width=“50dp”,都是控件大小已经确定的情况,都是精确尺寸。
(3)AT_MOST(最高两位10,表示最大模式):将控件的layout_width或layout_height指定为wrap_content时,控件大小随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父容器允许的最大尺寸即可。
(4)学习链接
Android之:了解MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST)
2.2 MeasureSpec和LayoutParams的对应关系
在view测量的时候,系统会将layoutparams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定view测量后的宽高,对于顶级view(DecorView)和普通的view来说,MeasureSpec的转换过程有些不同。当MeasureSpec一旦确定后,MeasureSpec就可以去为view测量了。
2.2.1 对于DecorView
(1)对于Decorview,其MeasureSpec由屏幕窗口的尺寸和其自身的Layoutparams来决定。在ViewRootImpl中的measureHierarchy方法中有这么一段代码。他展示了DecorViwew的MeasureSpec创建过程,其中desiredWindowWidth和desiredWindowHeight是屏幕窗口的尺寸,lp.width和lp.height是自身的Layoutparams:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
(2)接下来看getRootMeasureSpec()方法的实现
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// 精确模式,大小就是窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 最大模式,大小不定,但是不能超出屏幕的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 固定大小(比如100dp),精确模式,大小为LayoutParams中指定的大小
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
2.2.2 对于普通的view
(1)对于普通的view,指我们布局中的View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams来决定。View的measure过程由ViewGroup传递而来,先看下ViewGroup的measureChildWithMargis方法,此方法会在继承于ViewGroup的容器(例如:FrameLayout)的onMeasure()中被调用:
public class FrameLayout extends ViewGroup {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); // measureChildWithMargins()
}
}
}
}
(2)接下来看measureChildWithMargins()方法的实现
// 对子元素进行measure
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); // getChildMeasureSpec()
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
// 调用的子元素的measure()过程,详见3.1.1
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在调用子元素的measure方法之前会通过getChildMeasureSpec()方法得到子元素的MesureSpec。子元素的MesureSpec的创建和父容器的MesureSpec和子元素的LayoutParams有关,此外还和view的margin有关。
(3)具体看ViewGroup的getChildMeasureSpec()方法
// getChildMeasureSpec(父容器的MeasureSpec, 父容器的padding, 子元素自身的LayoutParams)
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); // 父容器模式
int specSize = MeasureSpec.getSize(spec); // 父容器大小
// 父容器可用的大小 = 父容器大小 - padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY: // 父容器--精确模式
if (childDimension >= 0) {
// 子元素固定大小(100dp)+ 精确模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子元素与父容器一样大小 + 精确模式
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子元素要确定自己大小,但不能比父容器大 + 最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST: // 父容器--最大模式
if (childDimension >= 0) {
// 子元素固定大小(100dp)+ 精确模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子元素要成为父容器大小,但父容器有最大尺寸,但不是固定的,限制子元素不能比父元素大 + 最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子元素要确定自己大小,但不能比父容器大 + 最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:// 父容器--未指定模式
if (childDimension >= 0) {
// 子元素固定大小(100dp)+ 精确模式
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;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
(4)总结,下图就表示如何生成子View的MeasureSpec:
(1)当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式,其大小是Layoutparams的大小;
(2)当View的宽/高是wrap_content时,不管父容器的模式是精确还是最大化模式,View的模式总是最大化,并且大小不能超过父容器的剩余空间。
(3)当View的宽/高是match_parent时,
①如果父容器是精确模式,那么View也是精确模式并且其大小是父容器的剩余空间;
②如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。
(5)UNSPECIFIED模式,那是因为这个模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式。
3 View的工作流程
3.1 View绘制流程第一步:measure过程
3.1.1 ViewRootImpl的performMeasure
// ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
mView其实就是DecorView,是一个FrameLayout;先看一下,ViewGroup、View、FrameLayout三个类的继承关系:FrameLayout继承了ViewGroup,而ViewGroup继承了View:
3.1.2 ViewGroup的measure过程
ViewGroup没有measure()方法,那直接看View的measure():
// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
虽然ViewGroup并没有onMeasure(),这是因为ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现,比如FrameLayout,LinearLayout等,为什么ViewGroup不像View一样对其onMeasure方法做统一的实现呢?那是因为不同的ViewGroup子类有不同的布局特性,这导致它们的测量细节各不相同,比如FrameLayout和Lineartayout这两者的布局特性显然不同,因此ViewGroup无法做统 一实现。
但DecorView是一个FrameLayout,下面就通过FrameLayout的onMeasure方法来分析ViewGroup的measure过程,其他Layout类型读者可以自行分析。
// FrameLayout.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// 遍历子View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 1、预测量measureChildWithMargins(),遍历测量子View
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 获取最大的子View的宽度、高度
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
...
}
}
// 增加padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
// 2、设定最大的子View的width、height为FrameLayout的宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
// 遍历子View
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
// 3、已知FrameLayout的MeasureSpec和子View的LayoutParmas 计算子View的MeasureSpec
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);
}
// 4、子View的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
由上面FrameLayout的测量流程:
(1)预测量measureChildWithMargins(),遍历测量子View,找出子view中最大的宽高;
(2)设定FrameLayout的测量宽高;
(3)遍历子View,计算子View的MeasureSpec = FrameLayout的MeasureSpec + 子View的LayoutParam;
(4)测量子View,child.measure();
measureChildWithMargins的作用是测量子View,并加上Padding、Margin的参考:
/**
* ViewGroup.java
* measureChildWithMargins思想就是取出子元素的LayoutParams,
* 然后再通过getChidMeasureSpec来创建子元素的MeasureSpec,
* 接着将MeasureSpec直接传递给View的measure方法来进行测量,
* 并加上Padding、Margin的参考。(getChidMeasureSpec在2.2.2中详细分析)
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
其实ViewGroup也有一个跟measureChildWithMargins类似的方法,measureChild(),也是同样是测量子View,不过不考虑margin:
/**
* ViewGroup.java
* 对比onMeasure与measureChildWithMargins、measureChild
* (1)onMeasure:由框架层去调用,默认实现。在重写时,可以调用measureChildWithMargins进行测量
* (2)measureChildWithMargins、measureChild:提供了默认的测量算法
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 调用的子元素的measure()过程
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
3.1.3 View的measure过程
上面是ViewGroup(FrameLayout)的onMeasure()方法,而普通View的onMeasure()比较简单,根据MeasureSpec设定自身的测量宽高,测量宽高并不一定等于最后的宽高,还有经过layout最后一步。
// View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
);
}
setMeasuredDimension()设定测量宽高:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
...
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
(1)先看getDefaultSize方法:
/**
* 只需要看AT_MOST和EXACTLY这两种情况,其实getDefaultSize返回的大小就是mesourSpec中的specSize,
* 而这个specSize就是view测量后的大小,但是View最终的大小是在layout阶段确定的
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize; // 当前View测量后的大小
break;
}
return result;
}
(2)再看getDefaultSize的参数
/**
* 如果View没有设置背景,那么返回android:minwidth这个属性所指定的值,这个值可以为0:
* 如果View设置了背景,则返回 android:minwidth和背景的最小宽度这两者中的最大值。
*/
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
3.1.4 小结
performMeasure是一个自上而下的测量流程,从DecorView开始,最后分发测量子View:
3.1.5 附加问题
1、直接继承View或者ViewGroup的控件,如果不在onMeasure中对wrap_content做特殊处理,那么当外界在布局中使用wrap_content时就相当于使用match_parent,为什么?
(1)因为如果View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽/高等于specSize,即是parentSize,而parentSize是父容器当前剩余的空间大小。所以,View的宽/高就等于父容器当前剩余的空间大小,这种效果和在布局中使用match_parent完全一致。如何解决这个问题呢?代码如下所示:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getMode(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (eightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}
2、为什么在onCreate、onStart、onResume中getMeasuredWidth为0?
因为View的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activiy执行了onCreate、onStart、onResume时某个View已经完毕了,如果View还没有测量完毕,那么获得的宽/高就是0。给出四种方法来解决这个问题:
(1)Activity/View的onWindowFocusChanged()
/**
* 添加窗体在视图初始化完成过后,这个时候去获取宽/高是没问题的。
* 需要注意的是,当Activity继续执行和暂停执行时,onWindowFocusChanged均会被调用,
* 如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用。
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
(2)view.post(runnable)
通过post可以将一个runnable投递到消息队列,然后等到Lopper调用runnable的时候,View也就初始化好了,典型代码如下:
@Override
protected void onStart() {
super.onStart();
mTextView.post(new Runnable() {
@Override
public void run() {
int width = mTextView.getMeasuredWidth();
int height = mTextView.getMeasuredHeight();
}
});
}
(3)ViewTreeObserver
当View树的状态发生改变或者View树内部的View的可见性发生改变,onGlobalLayout方法就会回调,因此这是获取View的宽高一个很好的例子,需要注意的是,伴随着View树状态的改变,这个方法也会被调用多次。
@Override
protected void onStart() {
super.onStart();
ViewTreeObserver observer = mTextView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = mTextView.getMeasuredWidth();
int height = mTextView.getMeasuredHeight();
}
});
}
(4)通过手动调用view.measure(int widthMeasureSpec , int heightMeasureSpec)测量View的宽高,要分情况来处理,根据View的LayoutParams来处理:
①match_parent:直接放弃,无法测量出具体的宽高,根据View的测量过程,构造这种measureSpec需要知道父容器的剩下空间parentSize,而我们无法知道parentSize的大小,所以理论上不可能测量出View的大小。
②具体的数值
// 宽高都是100dp
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
mTextView.measure(widthMeasureSpec,heightMeasureSpec);
③warp_content
// 最大是30个1(2^30-1),即是(1<<30)-1,在最大的模式下,用View理论上能支持最大值去构造MwasureSpec是合理的
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
mTextView.measure(widthMeasureSpec,heightMeasureSpec);
// 错误的方法:无法通过错误的MeasureSpec去得出合理的SpecMode,从而导致measure过程出错
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(-1, View.MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(-1, View.MeasureSpec.UNSPECIFIED);
mTextView.measure(widthMeasureSpec,heightMeasureSpec);
// 错误的方法:不能保证measure出正确的结果
mTextView.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
(5)参考链接
Android开发中getMeasuredWidth为0时的解决方法
3、如何解决ListView和ScrollView的嵌套冲突?
(1)解决方法
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
(2)原理解析
从源码角度解析ListView和ScrollView的嵌套冲突
3.2 View绘制流程第二步:layout过程
3.2.1 ViewRootImpl的performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
// 四个参数分别代表相对Parent的左、上、右、下坐标。而且左上都为0,右下分别为上面测量的width和height。
mView.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
mView其实就是DecorView,是一个FrameLayout;performLayout是Layout布局的开始,直接调用DecorView的layout()。
3.2.2 ViewGroup的layout过程
layout方法作用是确定了ViewGroup本身的位置,当ViewGroup的位置被确认之后,它的layout就会去遍历所有子元素并且调用onLayout方法。
/**
* ViewGroup.java
* layout方法作用是确定了ViewGroup本身的位置
*/
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
// 调用View.layout()
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
// View.java
public void layout(int l, int t, int r, int b) {
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); // 1.setFrame(l, t, r, b)
...
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b); // 2.onLayout(changed, l, t, r, b)
}
}
/**
* setFrame方法来设定自身最终的布局参数,即分别设置mLeft、mTop、mRight、和mBottom
* 四个顶点, 这些位置是相对父容器的,表示View在父容器内部的位置,
* 一旦确定后,那么View在父容器的位置也就确定了。
*/
protected boolean setFrame(int left, int top, int right, int bottom) {
...
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
}
/**
* onLayout方法,这个方法的用途是调用父容器确定子元素的位置,
* 和onMeasure类似,onLayout的具体位置实现同样和具体布局有关,
* 所有View和ViewGroup均没有真正的实现onLayout方法。
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
3.2.3 View的onLayout过程
看一下FrameLayout的onLayout,内部直接调用了layoutChildren(),顾名思义就是遍历子View,逐个布局:
/**
* FrameLayout.java
* onLayout方法则会确定所有子元素的位置
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
// layoutChildren()
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
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();
...
// child.layout
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
3.2.4 小结
performLayout也是一个自上而下的流程,从DecorView开始布局,分发到子View,最后确定布局。
3.2.5 附加问题
1、View的测量宽度getMeasureWidth()和屏幕实际显示的宽度getWidth()这两个方法有什么区别?
// getWidth()返回的刚好是View最终宽度的值
public final int getWidth() {
return mRight - mLeft;
}
// getHeight()返回的刚好是View最终高度的值
public final int getHeight() {
return mBottom - mTop;
}
在View的默认实现中,View的测量宽高和最终的是一样的,只不过一个是measure过程,一个是layout过程,而最终形成的是layout过程,即两者的赋值时机不同,测量宽高的赋值时机,稍微早一些。
所以,重写View的layout方法,导致getWidth()和getHeight() 各增加100px,而getMeasuredWidth()和getMeasuredHeight()始终没有变化。
public void layout(int l,int t,int r, int b){
super.layout(l,t,t+100,b+100);
}
另一种情况是在某种情况下,View需要多次measure才能确定自己的测量宽高,在前几次的测量过程中,其得出的测量宽高是不一致的但最终是一致的。
3.3 View绘制流程第三步:draw过程
3.3.1 ViewRootImpl的performDraw
ViewRootImpl的draw()调用了drawSoftware(),最后调用了View.draw():
// ViewRootImpl.java
private void performDraw() {
// draw(fullRedrawNeeded)
boolean canUseAsync = draw(fullRedrawNeeded);
}
// fullRedrawNeeded是否需要全部重新绘制视图
private boolean draw(boolean fullRedrawNeeded) {
final Rect dirty = mDirty;// mDrity表示脏区域
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
...
dirty.setEmpty();
} else {
...
// drawSoftware()
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
return useAsyncReport;
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
// DecorView的draw()
mView.draw(canvas);
}
3.3.2 View的draw过程
- 绘制背景
- If necessary, save the canvas’ layers to prepare for fading
- 绘制自己,onDraw(canvas)
- 绘制children,dispatchDraw(),如果当前的View没有子View就不需要进行绘制
- If necessary, draw the fading edges and restore layers
- 绘制装饰(滚动条)
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;
// 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);
drawAutofilledHighlight(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);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
return;
}
...
}
3.3.3 onDraw()和dispatchDraw()深入分析
(1)View中onDraw是一个空实现,由各种类型的View自定义重写该方法实现绘制。
(2)View中dispatchDraw()负责控制子View绘制的,在view是空实现,但是在ViewGroup中有具体的实现;这是可以理解的,毕竟只有ViewGroup才需要分发绘制这是可以理解的,毕竟只有ViewGroup才需要分发绘制:
// ViewGroup.java
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
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);
}
}
...
}
// 可见dispatchView()内部负责遍历子View,分别调用子VIew的draw()
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
3.3.4 小结
performDraw()从DecorView出发,根据脏区域执行绘制:绘制背景、绘制自身内容、绘制子View、绘制装:
3.3.5 附加问题
1、自定义的ViewGroup为什么不走onDraw?
public class MyViewGroup extends ViewGroup {
private static final String TAG = "MyViewGroup";
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw:执行了");
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
Log.d(TAG, "dispatchDraw:执行了");
}
}
// 第1种情况:不给MyViewGroup添加背景色
<com.seniorlibs.view.ui.MyViewGroup
android:layout_width="match_parent"
android:layout_height="100dp" />
MyViewGroup: dispatchDraw:执行了
// 第2种情况:给MyViewGroup添加背景色
<com.seniorlibs.view.ui.MyViewGroup
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/orange"/>
MyViewGroup: onDraw:执行了
MyViewGroup: dispatchDraw:执行了
结论:自定义的ViewGroup并不是不会走onDraw()方法,如果有背景色是要走的。
3.4 View绘制流程其他步骤
3.4.1 onFinishInflate()
(1)加载完成的意思,意思是:在setContentView之后会调用此方法;或者在自定义View :MyView mv = (MyView)View.inflate (context,R.layout.my_view,null)之后可以调用此方法
(2)但是在onFinishInflate()中不能获取view宽高,需要在onMeasure()之后获取,
setContentView > onFinishInflate > view绘制流程(performMeasure、performLayout、performDraw)
4 问题
4.1 View的requestLayout()、invalidate()与postInvalidate()方法区别
(1)requestLayout():从View树重新进行一次测量、布局、绘制这三个流程,最终就会显示子View的最终情况;
(2)invalidate():在主线程当中刷新当前View(或者内部调用,如:setVisiblity()),使当前View进行重绘,不会进行测量、布局流程;
(3)postInvalidate():在子线程当中刷新当前View,使当前View进行重绘,不会进行测量、布局流程。
4.1.1 requestLayout()方法
(1)view.requestLayout()方法
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
// 核心判断标记:设置当前view标记位,mPrivateFlags |= PFLAG_FORCE_LAYOUT
// 这个标记位的作用是:在View的measure流程中,如果当前View设置了该标记位,则进行测量流程
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
// 如果当前View在请求布局的时候,View树正在进行布局流程的话,
// 该请求会延迟到布局流程完成后或者绘制流程完成且下一次布局发现的时候再执行。
if (mParent != null && !mParent.isLayoutRequested()) {
// 向父容器请求布局
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,直到DecorView,DecorView又会传递给ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理:详细阅读 ### 1.2 (4)ViewRootImpl.requestLayout(),逐步开始View工作流程。
// ViewRootImpl.requestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
(2)View.measure()方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// 判断一下标记位,如果当前View的标记位为PFLAG_FORCE_LAYOUT,则进行测量流程
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT
|| widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 调用onMeasure(),对该View进行测量
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// 设置当前view标记位为mPrivateFlags |= PFLAG_LAYOUT_REQUIRED,
// 这个标记位的作用是:在View的layout流程中,如果当前View设置了该标记位,则会进行布局流程
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}
(3)View.layout()方法
public void layout(int l, int t, int r, int b) {
...
// 判断一下标记位,如果当前标记位为PFLAG_LAYOUT_REQUIRED,则对该View进行布局
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
// onLayout方法完成后,清除PFLAG_LAYOUT_REQUIRED标记位
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
// 最后清除PFLAG_FORCE_LAYOUT标记位
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
(4)总结一下invalidate方法,①当子View调用了requestLayout()方法后,会设置当前view标记位为mPrivateFlags |= PFLAG_FORCE_LAYOUT。②接着向父容器请求布局,requestLayout事件层层向上传递,最终触发performTraversals()方法,开始View的工作流程。③在View的measure流程中,判断如果当前View设置了PFLAG_FORCE_LAYOUT标记位,则进行测量流程;然后设置当前view标记位为mPrivateFlags |= PFLAG_LAYOUT_REQUIRED。④在View的layout流程中,判断如果当前View设置了PFLAG_LAYOUT_REQUIRED标记位,则对该View进行布局,接着清除PFLAG_FORCE_LAYOUT标记位。⑤最后,执行从draw流程。
4.1.2 invalidate方法
(1)View的invalidate(invalidateInternal)方法
// 只能在UI Thread中使用,针对整个View,View是可见的才有效,回调onDraw方法
public void invalidate() {
// invalidate的实质还是调运invalidateInternal方法
invalidate(true);
}
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
// 这是所有invalidate的终极调用方法
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
......
// 根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
......
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
// 设置刷新区域
damage.set(l, t, r, b);
// 传递调运Parent--ViewGroup的invalidateChild方法
p.invalidateChild(this, damage);
}
......
}
(2)父ViewGroup.invalidateChild()方法
// 在invalidate中调用父View的invalidateChild是一个从当前向上级父View回溯的过程
// 每一层的父View都将自己的显示区域与传入的刷新Rect做交集
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
......
do {
......
if (parent instanceof ViewRootImpl) {
}
// 循环层层上级调用,直到ViewRootImpl会返回null
parent = parent.invalidateChildInParent(location, dirty);
......
} while (parent != null);
}
(3)ViewRootImpl.invalidateChildInParent()方法
进行了offset和union对坐标的调整,然后把dirty区域的信息保存在mDirty中,最后调用了scheduleTraversals方法,触发performTraversals()方法,开始View的工作流程。
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
......
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
return null;
}
(4)总结一下invalidate方法,当子View调用了invalidate方法后,不断向父容器请求刷新,调用父容器方法通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中。最后调用了scheduleTraversals方法,触发performTraversals()方法,开始View的工作流程。由于没有添加measure和layout的标记位,因此measure、layout流程不会执行,而是直接从draw流程开始
4.1.3 postInvalidate方法
(1)View的postInvalidate()方法
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
final AttachInfo attachInfo = mAttachInfo;
// 只有attachInfo不为null的时候才会继续执行,即只有确保视图被添加到窗口的时候才会通知view树重绘,
// 因为这是一个异步方法,如果在视图还未被添加到窗口就通知重绘的话会出现错误
if (attachInfo != null) {
// 核心,实质就是调运了ViewRootImpl.dispatchInvalidateDelayed方法
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
(2)ViewRootImpl.dispatchInvalidateDelayed()
通过ViewRootImpl类的Handler发送了一个异步消息(MSG_INVALIDATE)到主线程,即通知主线程刷新视图,实质就是又在UI Thread中调运了View的invalidate()方法。
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
public void handleMessage(Message msg) {
......
switch (msg.what) {
case MSG_INVALIDATE:
//invalidate()
((View) msg.obj).invalidate();
break;
......
}
}
(4)总结一下postInvalidate方法,当在子线程中调用了postInvalidate方法后,通过子线程向主线程发送一个异步消息,即通知主线程刷新视图,实质就是在UI Thread中调运了View的invalidate()方法。
4.1.4 常见的引起invalidate方法操作的原因主要有:
- 直接调用invalidate方法:请求重新draw,但只会绘制调用者本身。
- 触发setSelection方法:请求重新draw,但只会绘制调用者本身。
- 触发setVisibility方法:当View可视状态在invisible转换visible时会间接调用invalidate方法,继而绘制该View。当View的可视状态在invisible\visible转换为gone状态时会间接调用requestLayout方法,同时由于View树大小发生了变化,所以会请求measure过程,同样只绘制需要“重新绘制”的视图。
- 触发setEnabled方法:请求重新draw,但不会重新绘制任何View包括该调用者本身。
- 触发requestFocus方法:请求重新draw,只绘制“需要重绘”的View。
4.1.5 学习链接
Android View 深度分析requestLayout、invalidate与postInvalidate
4.2 requestlayout, onlayout, onDraw, drawChild区别与联系
(1)requestLayout()方法:会导致调用Measure()方法和layout(),将会根据标志位判断是否需要onDraw();
(2)onLayout():摆放viewGroup里面的子控件;
(3)onDraw():绘制视图本身;(ViewGroup还需要绘制里面的所有子控件)
(4)drawChild(): 重新回调每一个子视图的draw方法,child.draw(canvas, this, drawingTime);
4.3 LinearLayout对比RelativeLayout(实质是性能对比)
(1)RelativeLayout会对子View做两次measure。这是为什么呢?首先RelativeLayout中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和布局中View的顺序并不相同,在确定每个子View的位置的时候,就需要先给所有的子View排序一下。又因为RelativeLayout允许A,B 2个子View,横向上B依赖A,纵向上A依赖B。所以需要横向纵向分别进行一次排序测量。
(2)LinearLayout,如果不使用weight属性,LinearLayout会在当前方向上进行一次measure的过程,如果使用weight属性,LinearLayout会避开设置过weight属性的view做第一次measure,完了再对设置过weight属性的view做第二次measure。由此可见,weight属性对性能是有影响的,而且本身有大坑,请注意避让。
(3)参考链接:Android中RelativeLayout和LinearLayout性能分析