1、一个Activity中View的层次是怎么样的?
Activity以及View设计成这么多层,是为了将功能(责任)划分开,关于各自的职责简介如下:
Activity:
- 封装
Activity
生命周期处理的逻辑,简化用户创建界面的流程; - 封装创建
Window
对象(其实是PhoneWindow
的对象)的逻辑,统一管理Window
; - 封装事件分发的逻辑,响应用户操作;
- 封装各种启动模式对应的相关逻辑;
- 封装页面间数据传递相关的逻辑;
Window
- 封装xml布局解析成View的逻辑;
- 封装了界面的样式、布局(包括标题栏、内容);
DecorView
- 封装了修改状态栏、导航栏颜色的方法(导航栏和状态栏的透明的并浮在DecorView的上面);
- 封装了初始化试图树根的逻辑,还有布局、绘制、事件分发等逻辑。其中发起渲染的逻辑用到了ViewRootImpl,它是连接
Window
与DecorView
的桥梁;
2、View是如何显示出来的?
基本流程总结为如下流程图:
(1)Activity
执行attach()
方法会创建PhoneWindow
对象,PhoneWindow
对象会调用getDecorView
方法创建DecorView对象;
(2)Activity
执行onCreate()
方法会执行setContentView()
,其中会创建subDecor
(包含contenrRoo
t、标题栏、状态栏)。在此之前若发现没有DecorView
就会先去创建DecorView
;
(3)完成subDecorView
之后。通过LayoutInflate
方法加载我们设置的布局,并解析成ViewGroup
,然后通过addView()
添加到contentRoot
的contentParent
之中;
(4)handleResumeActivity
方法调用完onResume
之后,会通过PhoneWindow
获取WindowManagerImpl
来调用addView
方法,其内部会调用WindowManagerGlobal.addView
方法,最后调到ViewRootImpl
的 setView
方法;
(5)setView
中会调用requestLayout
方法,检查是否在主线程,然后调用scheduleTraversals()
方法;
(6)scheduleTraversals
方法是通过Handle
实现,目的是确保每一次绘制都在vsync
脉冲信号发出时调用 doTraversal;
(7)doTraversal
方法会调用 performTraversals
,先调用DecorView
的dispatchAttachedToWindow
方法,分发onAttachedToWindow
事件;然后执行 measure、layout、draw
流程;
(8)在setView
方法中将当前PhoneWindow
添加到WindowManagerService(WMS)
上;
3、View是如何绘制的(ViewRootImpl.performTraversals())?
先来看下performTraversals()
中的主要执行流程:
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
//执行测量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局流程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//执行绘制流程
performDraw();
}
ViewRootImpl.performTraversals()
中执行performMeasure()、performLayout()、performDraw()
最终会执行到View的onMeasure()、onLayout()、onDraw()
等。performMeasure()、performLayout()、performDraw()的执行本质是递归执行的结构。
3.1 measure之——关于MeasureSpec的理解
MeasureSpec表示的是一个View的测量模式以及这个View的测量的得到长度(宽度或者高度)值【非真实值,需要根据测量模式具体的去调整】,如何去理解呢?
测量值
- 简单可以理解成View的暂定长度,它是一个参考值;
测量模式
EXACTLY
(确定,后用E表示): 表示这个测量值就是View真实的长度;AT_MOST
(最多,后用A表示): 表示的是View的真实长度不能超过这个测量值;UNSPECIFIED
(不确定,后用U表示): 表示View的长度不受这个测量值限制,使用到此测量模式的的控件很少,比如ScollerView、RecycleView这类可以滑动的View会用到,因为子View可以无限高,比父View高得多;
关于子View的MeasureSpec确定的规则
View的真实长度是由父ViewGroup的MeasureSpec和自己的LayoutParams决定的,关系如下:
子View参数/父ViewGroup的mode | EXACTLY,parentSize | AT_MOST,parentSize | UNSPECIFIED,parentSize |
---|---|---|---|
具体长度值(dp/px) | E,具体长度值 | E,具体长度值 | E,具体长度值 |
match_parent | E,parentSize-padding | A,parentSize-padding | U,未知 |
wrap_content | A,parentSize-padding | A,parentSize-padding | U,未知 |
3.1 measure之——measure()执行细节
在measure
中,父的ViewGroup
会通过for循环调用子ViewGroup
或者View
的measureChildren()
方法,measureChildren()
会调用View的measure()
方法,简略代码如下:
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
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);
}
}
}
ViewGroup
中,measureChild()
方法最终会调用到View
的measure()
方法:
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//根据父ViewGroup的MeasureSpec,结合当前layoutParams设置的padding值会得到当前子View真实的测量宽度和高度
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在基础的View
类中,我们可以看到onMeasure()
方法的实现为:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
其中计算View
的宽高是通过getDefaultSize
来确认的,然后使用setMeasuredDimension
来设置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;
}
在View
的onMeasure
方法中,getDefaultSize
入参的第一个为getSuggestedMinimumHeight
,第二个为传入的measureSpec
的size
。当测量模式为AT_MOST和EXACTLY
的时候,取值为传入的MeasureSpec
对象的size值;当测量模式为UNSPECIFIED
的时候,传入的值为getSuggestedMinimumHeight
的值。可见基础的View
实现的时候MATCH_PARENT
和WRAP_CONTENT
的效果其实是一样的,都是测量得到的最大值,为什么是这样的呢?因为在WRAP_CONTENT
的情况之下,实际的宽高是要根据内容内容的大小来具体确认的,而基础View
的实现是没有具体的内容的,因此没有办法设置成具体的值。
因此需要注意的是,在完全的自定义View的时候一定要去重写onMeasure()方法!!!
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight,mBackground.getMinimumHeight());
}
子View
的MeasureSpec
是由父View的传入的MeasureSpec
和本身的LayoutParams
来决定的,那最顶层的DecoreView
是从哪了获取的MeasureSpec
呢?
Activity
会通过WindowManager
将DecoreView
对象添加到window
之中,代码如下:
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
}
performTraversals()
是由ViewRootImpl
开始执行的,关键代码如下:
// ViewRootImpl.java
private void performTraversals() {
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
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;
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
从代码中可以看出,最初的MeasureSpec
是由window
的大小和LayoutParams
来直接初始化生成的。
3.2 layout执行细节
首先看performTraversals()
中执行的performLayout()
方法:
// ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
然后执行到View
的layout
方法:
// View.java
public void layout(int l, int t, int r, int b) {
...
// 通过setFrame方法来设定View的四个顶点的位置,即View在父容器中的位置
boolean changed = isLayoutModeOptical(mParent) ?
set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
...
onLayout(changed, l, t, r, b);
...
}
// 空方法,子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup
// 中所有View控件布局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
参考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,)
}
}
其中,layoutVertical()
的实现为:
// layoutVertical核心源码
void layoutVertical(int left, int top, int right, int bottom) {
...
final int count = getVirtualChildCount();
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.getMeasureWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
...
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
// 为子元素确定对应的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
// childTop会逐渐增大,意味着后面的子元素会被
// 放置在靠下的位置
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child,i)
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
从上面的逻辑可以看出,ViewGroup中实现的onLayout()方法主要做的是在父ViewGroup圈的范围之内设置自身的margin值,然后调用子View的layout()方法进一步布局【layout()中会调用onLayout()方法】,如此便实现了层层递归调用布局。!!!
View.java
的的实现是空实现,具体我们可以参考TextView
的onLayout
方法,看它都做了些什么:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mDeferScroll >= 0) {
int curs = mDeferScroll;
mDeferScroll = -1;
bringPointIntoView(Math.min(curs, mText.length()));
}
// Call auto-size after the width and height have been calculated.
autoSizeText();
}
这里面主要做了些内容排布和变更的操作。在这个方法中我们没有看到左上右下坐标点的使用,是因为定位的操作在layout()
中完成了,如下:
//View.java
boolean changed = isLayoutModeOptical(mParent)? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
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);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
// If we are visible, force the DRAWN bit to on so that
// this invalidate will go through (at least to our parent).
// This is because someone may have invalidated this view
// before this call to setFrame came in, thereby clearing
// the DRAWN bit.
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
// parent display list may need to be recreated based on a change in the bounds
// of any child
invalidateParentCaches();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
3.3 draw执行流程
private void performDraw() {
...
draw(fullRefrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset,
scalingRequired, dirty)) {
return;
}
...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo,
int xoff, int yoff, boolean scallingRequired, Rect dirty) {
...
mView.draw(canvas);
...
}
// 绘制基本上可以分为六个步骤
public void draw(Canvas canvas) {
...
// 步骤一:绘制View的背景
drawBackground(canvas);
...
// 步骤二:如果需要的话,保持canvas的图层,为fading做准备
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left, top, right, top + length, null, flags);
...
// 步骤三:绘制View的内容
onDraw(canvas);
...
// 步骤四:绘制View的子View
dispatchDraw(canvas);
...
// 步骤五:如果需要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// 步骤六:绘制View的装饰(例如滚动条等等)
onDrawForeground(canvas)
}
关于setWillNotDraw
的作用:
// 如果一个View不需要绘制任何内容,那么设置这个标记位为true以后,
// 系统会进行相应的优化。
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
- 默认情况下,
View
没有启用这个优化标记位,但是ViewGroup
会默认启用这个优化标记位。 - 当我们的自定义控件继承于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化。
- 当明确知道一个
ViewGroup
需要通过onDraw
来绘制内容时,我们需要显示地关闭WILL_NOT_DRAW
这个标记位。