View的工作流程
1. DecorView被加载到Window中
当DecorView被创建后,要加载到Window中,会调用ActivityThread中的handleLaunchActivity()方法
handleLaunchActivity()方法
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
···
final Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
if (!r.activity.mFinished && pendingActions != null) {
pendingActions.setOldState(r.state);
pendingActions.setRestoreInstanceState(true);
pendingActions.setCallOnPostCreate(true);
}
}
···
上面代码第1行调用performLaunchActivity()方法来创建Activity,这里会调用Ac的onCreate()方法,从而完成DecorView的创建
handleResumeActivity()方法
由于Android更新后这个方法不再由handleLaunchActivity()方法调用,但这个方法仍然很重要
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
···
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
···
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
···
上面代码第1行调用performResumneActivity()方法,这个方法里会调用Ac的onResume()方法
第一个if块中第2行getDecorView()方法得到DecorView,第4行getWindowManager()得到WindowManager
WindowManager的实现类是WindowManagerImpl,所以倒数第1个if块中实际调用的是WIndowManagerImpl的addView()方法
WIndowManagerImpl的addView()方法
public final class WindowManagerImpl implements WindowManager {
@UnsupportedAppUsage
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
···
在WindowManagerImpl的addView()方法中,又调用了WIndowManagerGlobal的addView()方法,如下
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {
···
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
···
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
上面代码同步块中第1行创建了ViewRootImpl实例,try/catch中调用了ViewRootImpl的setView()方法,并将DecorView作为参数传进去,这样就把DecorView加载到了window中
但此时的界面什么都不会显示,因为view的工作流程还没有开始
2. ViewRootImpl的performTraversals()方法
ViewRootImpl通过setView()将DecorView加载到Window,ViewRootImpl还有一个performTraversals()方法,这个方法使得ViewTree开始View的工作流程
private void performTraversals() {
···
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
···
if (didLayout) {
performLayout(lp, mWidth, mHeight);
···
if (!cancelDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
三个if块中主要执行了3个方法,分别是performMeasure()、performLayout()、performDraw()。其内部又会调用View的measure()、layout()、和draw()方法。我们可以发现performMeasure()方法需要传进两个参数childWidthMeasureSpec,childHeightMeasureSpec。了解这两个参数需要了解MeasureSpec
3. 理解MeasureSpec
MeasureSpec是View的内部类,封装了View的规格尺寸,包括宽和高的信息,它的作用是在Measure的流程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再onMeasure()方法中根据这个MeasureSpec来确定View的宽和高。MeasureSpec的代码如下
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
从MeasureSpec的常量可以看出,它代表32位int值,其中高2位代表了SpecMode,低30位则代表SpecSize。SpecMode指的是测量模式,SpecSize指的是测量大小,SpecMode有三种模式,如下
UNSPECIFIED:未指定模式,View任意大小,父容器不做限制,一般用于系统测量
AT_MOST:最大模式,对应于wrap_content属性,子View的最终大小是父View指定的SpecSize值,并且子view的大小不能大于这个值
EXACTLY:精确模式,对应于match_parent属性和具体的数值,父容器测量出View所需要的大小,也就是SpecSize值、
对于每一个View,都持有一个MeasureSpec,而MeasureSpec保存了该View的尺寸规格,在View的测量流程中,通过makeMeasureSpec来保存宽和高信息,通过getMode或getSize得到模式的宽和高。MeasureSpec是受自身LayoutParams和父容器MeasureSpec共同影响的,作为顶层View的DecorView是如何确定自己的MeasureSpec的呢?我们回到的ViewRootImpl的performTraversals()方法,查看getRootMeasureSpec()方法做了什么
getRootMeasureSpec()方法
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
getRootMeasureSpec的第一个参数是窗口尺寸,所以对于DecorView来说,它的MeasureSpec由自身的LayoutParams和窗口的尺寸决定,这一点和普通View是不同的。往下就会看到根据自身的LayoutParams来得到不同的MeasureSpec
了解了DecorView,让我们回到performTraversals()方法,查看第1个重要方法performMeasure()
performMeasure()方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
代码很简单,我们知道performMeasure里调用的是mView的measure()方法,接下来学习一下measure()方法的工作流程
4. View的measure流程
View的measure()方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
···
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
···
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
···
上面代码第1个if块中很明显调用了onMeasure()方法,那么我们就来看看onMeasure()做了什么
onMeasure()方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
很明显的两个方法setMeasuredDimension()和getDefaultSize()。接下来查看setMeasuredDimension()和getDefaultSize()
setMeasuredDimension()方法
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
measureWidth和measureHeight用来设置View的宽和高。
getDefaultSize()方法
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;
break;
}
return result;
}
上面代码第2行SpecMode是View的测量模式,SpecSize是View的测量大小,这里根据不同模式返回不同result值,也就是SpecSize。
AT_MOST和EXACTLY模式下都返回SpecSize,即View在这两种模式下的测量宽和高直接取决于SpecSize。也就是说对于一个直接继承自View的自定义View来说,它的wrap_content和match_parent属性的效果是一样的。因此如果要实现自定义View的wrap_content,就要重写onMeasure()方法,并对自定义View的wrap_content值进行处理。
而在UNSPECIFIED模式下返回的是getDefaultSize方法的第一个参数size值,size值从onMeasure()方法来看是getSuggestedMinimumWidth()方法或getSuggestedMinimumHeight()方法得到的,我们来看看getSuggestedMinimumWidth()方法做了什么,这两个方法原理是一样的
getSuggestedMinimumWidth()方法
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
如果没有设置View的背景,则取值为mMinWidth,这个值是可以设置的,对应于Android:minWidth这个属性设置的值或者View的setMinimunWidth()方法,如果不指定的话默认为0。setMinimumWidth()方法如下
setMinimumWidth()方法
public void setMinimumWidth(int minWidth) {
mMinWidth = minWidth;
requestLayout();
}
如果View设置了背景,则取值为max(mMinWidth,mBackground.getMinimumWidth()),也就是取mMinWidth和mBackground.getMinimumWidth()的最大值,这个mBackground是Drawable类型的,刚才说了mMinWidth,现在看看getMinimumWidth()方法
getMinimumWidth()方法
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
intrinsicWidth得到的是这个Drawable的固有宽度,如果固有宽度大于0则返回固有宽度,否则返回0。
总结一下,getSuggestedMinimumWidth()方法:如果View没有设置背景,则返回mMinWidth。如果设置了背景,就返回mMinWidth和Drawable的最小宽度之间的最大值
View的measure流程到此结束
5. ViewGroup的measure流程
接下来看看ViewGroup的measure流程。
对于ViewGroup,它不止要测量自身,还要遍历地调用子元素的measure()方法。ViewGroup中没有定义onMeasure()方法,却定义了measureChildren()方法
measureChildren()方法
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
遍历子元素调用measureChild()方法,如下
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上面代码第1行获取子元素的LayoutParams属性。下面两行获取子元素的MeasureSpec并最后调用measure()方法进行测量,getChildMeasureSpec()方法做了什么呢?如下
getChildMeasureSpec()方法
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
很显然,每一个大的case下都会有child的LayoutParams属性来得出子元素的MeasureSpec属性
有一点需要注意的是,如果父容器的MeasureSpec属性为AT_MOST,子元素的LayoutParams属性为wrap_content,那根据上面第2个case最后一个else if块处的代码,我们会发现子元素的MeasureSpec属性也为AT_MOST,他的SpecSize值为父容器的SpecSize减去padding值。换句话说,这和子元素设置LayoutParams属性为match_parent效果是一样的。为了解决这个问题,需要在LayoutParams属性为wrap_content时指定一下默认的宽和高。ViewGroup并没有提供onMeasure()方法,而是让其子类各自实现测量的方法,究其原因就是ViewGroup有不同的布局需要,很难统一
接下来我们简单分析一下ViewGroup的子类LinearLayout的measure流程。先看看他的onMeasure()方法
LinearLayout的onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
这个方法逻辑很简单,如果是垂直方向就调用measureVertical()方法,否则就调用measureHorizontal()方法,我们来看看measureVertical()方法
LinearLayout的measureVertical()方法
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
float totalWeight = 0;
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
// The heightMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal height. We'll restore the original height of 0
// after measurement.
lp.height = LayoutParams.WRAP_CONTENT;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
···
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
···
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
···
}
这段代码很长,简单说一下,开始定义了mTotalLength用来存储LinearLayout在垂直方向上的高度,然后for循环遍历子元素,根据子元素的MeasureSpec模式分别计算每个子元素的高度。如果是wrap_content,则将每个子元素的高度和margin垂直高度等值相加并赋给mTotalLength。当然最后还要加上垂直方向的padding值。如果布局高度设置为match_parent或者具体数值,则和view的测量方法是一样的
ViewGroup的measure流程到此结束
6. View的layout流程
layout()方法的作用是确定元素的位置。ViewGroup中的layout()方法用来确定子元素的位置。View中的layout()方法用来确定自身位置。
来看看View的layout()方法
View的layout()方法
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;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
···
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList listenersCopy =
(ArrayList)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);
}
}
}
final boolean wasLayoutValid = isLayoutValid();
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
···
}
layout()方法四个参数l、t、r、b分别代表是View从左、上、右、下相对于其父容器的距离,接着来看上面代码中间部分调用的setFrame()方法做了什么
setFrame()方法
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
···
}
return changed;
}
setFrame()方法用传进来的l、t、r、b四个参数分别初始化mLeft,mTop,mRight,mBottom四个值,就确定了该View在父容器中的位置
调用了setFrame()方法后,View的layout()方法会接着调用onLayout()方法
View的onLayout()方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
onLayout()方法是一个空方法,确定位置时根据不同的控件有不同的实现,所以在View和ViewGroup中均没有实现onLayout()的方法,既然如此,我们看看LinearLayout的onLayout()方法
LinearLayout的onLayout()方法
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
与onMeasure()方法类似,根据方向调用不同方法,仍然查看垂直方向的layoutVertical()方法
LinearLayout的layoutVertical()方法
void layoutVertical(int left, int top, int right, int bottom) {
···
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
这个方法会遍历子元素并在上面代码末尾部分调用setChildFrame()方法,其中childTop值是不断累加的,这样子元素才会依次按照垂直方向一个接一个排列下去而不是重叠,接着看setChildFrame()方法
setChildFrame()方法
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
在这个方法中调用子元素的layout()方法确定自己的位置
7. View的draw流程
官方清楚的说明了draw的每一步流程
- 如果需要,则绘制背景
- 保存当前canvas层
- 绘制View的内容
- 绘制子View
- 如果需要,则绘制View的褪色边缘,类似于阴影效果
- 绘制装饰,比如滚动条
这里第2步和第5步可以跳过,重点分析其他步骤
7.1 绘制背景
绘制背景调用了View的drawBackground()方法
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
可以看到if语句块中考虑了背景偏移参数scrollX和scrollY,如果有偏移值不为0,则会在偏移后的canvas绘制背景
7.3 绘制View的内容
这里调用了View的onDraw方法,这个方法是个空实现,因为不同的View有不同的内容,需要我们自己去实现,在自定义View里重写该方法
onDraw()方法
protected void onDraw(Canvas canvas) {
}
7.4 绘制子View
调用了dispatchDraw()方法,这个方法也是一个空实现
protected void dispatchDraw(Canvas canvas) {
}
ViewGroup重写了这个方法,紧接着看ViewGroup的dispatchDraw()方法
ViewGroup的dispatchDraw()方法
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;
}
}
···
截取关键部分,在dispatchDraw()方法中对子类View进行遍历,并在第一个if块中调用drawChild()方法
drawChild()方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
child是View的对象,说明这里调用了View的draw()方法
View的boolean draw()方法
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
···
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((RecordingCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
} else if (cache != null) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
// no layer paint, use temporary paint to draw bitmap
Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
cachePaint.setAlpha((int) (alpha * 255));
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
// use layer paint to draw the bitmap, merging the two alphas, but also restore
int layerPaintAlpha = mLayerPaint.getAlpha();
if (alpha < 1) {
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
}
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
if (alpha < 1) {
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
}
我们看重点分析,上面代码第1个if块中判断是否有缓存,如果没有则正常绘制,如果有则利用缓存显示
中间部分,第二个else块中的draw(canvas)则是绘制自身,调用下面同名draw()方法
View的void draw()方法
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
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)
* 7. If necessary, draw the default focus highlight
*/
// Step 1, draw the background, if needed
int saveCount;
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
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);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
7.6 绘制装饰
绘制装饰的方法为View的onDrawForeground()方法
onDrawForeground()方法
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
很明显这个方法用于绘制ScrollBar以及其他装饰,并将他们绘制在视图内容的上层
到这里View的绘制流程结束了
同样,我们简单总结串联一下使用的方法有哪些吧
加载DecorView到window:
handleLaunchActivity()——handleResumeActivity()——WIndowManagerImpl的addView()——WIndowManagerGlobal的addView
理解MeasureSpec的流程:
ViewRootImpl的performTraversals()——getRootMeasureSpec()——MeasureSpec
View绘制的measure流程:
ViewRootImpl的performTraversals()——performMeasure()——View的measure()——onMeasure()——getSuggestedMinimumWidth()——setMinimumWidth() ||
getMinimumWidth()
onMeasure()——setMeasuredDimension()
onMeasure()——getDefaultSize()
View的layout流程:
View的layout()——setFrame()
View的layout()——View的onLayout()
View的draw流程:
View的drawBackground()——onDraw()——dispatchDraw()——ViewGroup的dispatchDraw()——drawChild()——View的boolean draw()——View的void draw()——View的onDrawForeground()
ViewGroup绘制的measure流程:
ViewGroup的measure()——measureChild()——getChildMeasureSpec()
LinearLayout的measure流程:
LinearLayout的onMeasure()——measureVertical() || measureHorizontal()
LinearLayout的layout流程
LinearLayout的onLayout()——LinearLayout的layoutVertical() || layoutHorizontal()——setChildFrame()
本文摘抄自《Android进阶之光——刘望舒》,为自己学习路程中的记录,不以盈利为目的。
欢迎指正。