内容简介:在 Android 的知识体系中,上面的代码说明,在
在 Android 的知识体系中, View 扮演者很重要的角色。 View 是 Android 在视觉上的呈现。本文结合 android-28 的源码来分析 View 的绘制过程。
ViewRootImpl
ViewRootImpl 类是连接 WindowManager 和 DecorView 的纽带, View 的绘制流程均是通过 ViewRootImpl 来完成的。
// ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
// 获取 WindowManager 及 DecorView
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;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 将 DecorView 添加到当前 Window 中
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
...
}
复制代码
上面的代码说明,在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 通过 WindowManager 添加到 Window 中。
// WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
复制代码
可以知道最终是通过 WindowManagerGlobal 的 addView 方法来将 DecorView 添加到 Window 中
// WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
// 初始化 ViewRootImpl 并将 ViewRootImpl 对象和 DecorView 建立关联
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);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
复制代码
上述代码创建了 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联。最终在 setView 方法中,会执行 ViewRootImpl 的 requestLayout 方法来执行 View 的绘制流程
// ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
复制代码
scheduleTraversals 方法最终会调用 performTraversals 方法,经过 measure 、 layout 和 draw 三个过程才能最终将一个 View 绘制出来
View View View
如图所示, performTraversals 会依次调用 performMeasure 、 performLayout 和 performDraw 三个方法,这三个方法分别完成顶级 View 的 measure 、 layout 和 draw 这三大流程。其中在 performMeasure 中会调用 measure 方法,在 measure 方法中又会去调用 onMeasure 方法,在 onMeasure 方法中又会对所有的子元素进行 measure 过程,这个时候 measure 流程就从父容器传递到了子元素中了,这样就完成了一次 measure 过程。接着子元素会重复父容器的 measure 过程,如此反复就完成了整个 View 树的遍历。通过 performLayout 和 performDraw 的传递流程跟 performMeasure 类似
MeasureSpec
为了更好地理解 View 的测量过程,我们还需要理解 MeasureSpec 。 MeasureSpec 参与了 View 的 measure 过程,在很大程度上决定了一个 View 的尺寸规格,但父容器也会影响 View 的 MeasureSpec 的创建过程。 在测量过程中,系统会将 View 的 LayoutParams 根据父容器所试驾的规则转换成对应的 MeasureSpec ,然后再根据这个 MeasureSpec 来测量出 View 的宽和高。
// View.java
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 {}
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;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
int size = getSize(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return makeMeasureSpec(size, UNSPECIFIED);
}
size += delta;
if (size < 0) {
Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
") spec: " + toString(measureSpec) + " delta: " + delta);
size = 0;
}
return makeMeasureSpec(size, mode);
}
public static String toString(int measureSpec) {
int mode = getMode(measureSpec);
int size = getSize(measureSpec);
StringBuilder sb = new StringBuilder("MeasureSpec: ");
if (mode == UNSPECIFIED)
sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
sb.append("EXACTLY ");
else if (mode == AT_MOST)
sb.append("AT_MOST ");
else
sb.append(mode).append(" ");
sb.append(size);
return sb.toString();
}
}
复制代码
MeasureSpec 代表一个 32 位的 int 值,高 2 位代表 SpecMode , 低 30 位代表 SpecSize
- SpecMode: 测量模式
- SpecSize: 在某种测量模式下的规格大小
MeasureSpec 通过将 SpecMode 和 SpecSize 打包成一个 int 值来避免过多的对象内存分配 。 makeMeasureSpec 是打包方法, getMode 和 getSize 则为解包方法。
SpecMode 有三类,每一类都标识特殊的含义
UNSPECIFIED
父容器不对 View 有任何限制,要多大给多大,这种情况一般用于系统内部,标识一种测量的状态
EXACTLY
父容器已经检测出 View 所需要的精确大小,这个时候 View 的最终大小就是 SpecSize 所指定的值。 它对应于 LayoutParams 中的 match_parent 和具体的数值这两种模式
AT_MOST
父容器指定了一个可用大小即 SpecSize , View 的大小不能大于这个值,具体是什么值要看不同 View 的具体实现。它对应于 LayoutParams 中的 wrap_content
MeasupreSpec 和 LayoutParams
MeasureSpec 不是唯一由 LayoutParams 决定的, LayoutParams 需要和父容器一起才能决定 View 的 MeasureSpec ,从而进一步决定 View 的宽和高。
对于 DecorView ,其 MeasureSpec 由窗口的尺寸和其自身的 LayoutParams 来共同确定;对于普通的 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同局诶的那个, MeasureSpec 一旦确定后, onMeasure 中就可以确定 View 的测量宽和高
DecorView 创建 MeasureSpec
对于 DecorView 来说,它的 MeasureSpec 创建过程是由 getRootMeasureSpec 方法来完成的
// ViewRootImpl.java
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: // 精确模式,大小为 LayoutParams 中指定的大小
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
复制代码
通过上述代码, DecorView 的 MeasureSpec 的产生过程就很明确了,具体来说其遵守如下规则,根据它的 LayoutParams 中的宽和高参数来划分
- LayoutParams.MATCH_PARENT: 精确模式,大小就是窗口大小
- LayoutParams.WRAP_CONTENT: 最大模式,大小不定,但是不能超过窗口的大小
- 固定大小(如 100dp): 精确模式,大小为 LayoutParams 中指定的大小
ViewRootImpl 在 performTraversals 方法中调用 getRootMeasureSpec 获取到 childWidthMeasureSpec 和 childHeightMeasureSpec 后,会传给 performMeasure 方法,最终调用 DecorView 的 measure 方法
普通 View 创建 MeasureSpec
对于普通 View 来说,即布局中的 View , View 的 measure 过程由 ViewGroup 传递而来,在 ViewGroup 的 measureChildWithMargins 方法
// ViewGroup.java
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);
}
复制代码
measureChildWithMargins 方法一般会在自定义 Layout 组件的 onMeasure 方法中调用(如 FrameLayout, LinearLayout),来测量子元素的规格。在调用子元素的 measure 方法之前会先通过 getChildMeasureSpec 方法来得到子元素的 MeasureSpec 。通过上面代码可知,子元素的 MeasureSpec 的创建和父容器的 MeasureSpec 和子元素本身的 LayoutParams 有关,此外还和 View 的 margin 及 padding 有关
// ViewGroup.java
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 父容器的 mode 和 size
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);
}
复制代码
getChildMeasureSpec 函数主要的作用是根据父容器的 MeasureSpec 同时结合 View 本身的 LayoutParams 来确定子元素的 MeasureSpec 。
当 View 采用固定宽和高的时候,不管父容器的 MeasureSpec 是什么, View 的 MeasureSpec 都是精确模式并且其大小遵循 LayoutParamas 中的大小。当 View 的宽和高是 match_parent 时,如果父容器的模式是精确模式,那么 View 也是精确模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么 View 也是最大模式并且其大小不会超过父容器的剩余空间。如果父容器是最大模式,那么 View 也是最大模式并且其大小不会超过父容器的剩余空间。当 View 的宽和高是 wrap_content 时,不管父容器的模式是精准还是最大化, View 的模式总是最大化并且大小不能超过父容器的剩余空间。
以上所述就是小编给大家介绍的《基于源码分析 Android View 绘制机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- View绘制流程源码分析
- Android 源码分析三 View 绘制
- Android源码分析之View绘制流程
- YYText 源码剖析:CoreText 与异步绘制
- RecyclerView 源码深入解析——绘制流程、缓存机制、动画等
- Chrome 小恐龙游戏源码探究五 -- 随机绘制障碍
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java 8函数式编程
[英] Richard Warburton / 王群锋 / 人民邮电出版社 / 2015-3 / 39.00元
通过每一章的练习快速掌握Java 8中的Lambda表达式 分析流、高级集合和其他Java 8类库的改进 利用多核CPU提高数据并发的性能 将现有代码库和库代码Lambda化 学习Lambda表达式单元测试和调试的实践解决方案 用Lambda表达式实现面向对象编程的SOLID原则 编写能有效执行消息传送和非阻塞I/O的并发应用一起来看看 《Java 8函数式编程》 这本书的介绍吧!