1. 概述
源码分析基于 Android O
如上图, Activity 由 window 组成, Activity 内部有个 Window 成员, 它的实例为 PhoneWindow,PhoneWindow 有个 DecorView(这个也是最顶层的 View), 这个 DecorView 就是存放布局文件的, 里面有 TitleActionBar 和 ContentView(这个就是我们我们 setContentView 的布局)
1.ViewRoot 对应于 ViewRootImpl 类, 是连接 WindowManager 和 DecorView 的纽带, View 的三大流程均是通过 ViewRoot 来完成的在 ActivityThread 中, 当 Activity 对象被创建完毕后, 会将 DecorView 添加到 Window 中, 同时会创建 ViewRootImpl 对象, 并将 WindowManager 对象和 DecorView 建立关联
2.View 的绘制流程从 ViewRoot 的 performTraversals 开始, 经过 measurelayout 和 draw 三个过程才可以把一个 View 绘制出来, 其中 measure 用来测量 View 的宽高, layout 用来确定 View 在父容器中的放置位置, 而 draw 则负责将 View 绘制到屏幕上
3.performTraversals 会依次调用 performMeasureperformLayout 和 performDraw 三个方法, 这三个方法分别完成顶级 View 的 measurelayout 和 draw 这三大流程其中 performMeasure 中会调用 measure 方法, 在 measure 方法中又会调用 onMeasure 方法, 在 onMeasure 方法中则会对所有子元素进行 measure 过程, 这样就完成了一次 measure 过程; 子元素会重复父容器的 measure 过程, 如此反复完成了整个 View 数的遍历
整个绘制流程是在 ViewRoot 中的 performTraversals()方法展开的 我们先看看 ViewRootImpl#performTraversals()
- private void performTraversals() {
- ...
- if (!mStopped) {
- int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); // 1
- int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
- performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- }
- if (didLayout) {
- performLayout(lp, desiredWindowWidth, desiredWindowHeight);
- ...
- }
- if (!cancelDraw && !newSurface) {
- if (!skipDraw || mReportNextDraw) {
- if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
- for (int i = 0; i < mPendingTransitions.size(); ++i) {
- mPendingTransitions.get(i).startChangingAnimations();
- }
- mPendingTransitions.clear();
- }
- performDraw();
- }
- }
- ...
- }
performTraversals()方法非常的长, 不过主要就是递归去测量, 布局, 和绘图
2.View 的测量流程
2.1 MeasureSpec
在介绍测量流程之前, 我们先来介绍下 MeasureSpec, 它用来把测量要求从父 View 传递给子 View 我们知道 View 的大小最终由子 View 的 LayoutParams 与父 View 的测量要求公共决定, 测量要求指的 就是这个 MeasureSpec, 它是一个 32 位 int 值
高 2 位: SpecMode, 测量模式 低 30 位: SpecSize, 在特定测量模式下的大小
我们可以去看看 MeasureSpec 的源码:
- View#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 {}
- /**
- * 父 View 不对子 View 做任何限制, 需要多大给多大, 这种情况一般用于系统内部, 表示一种测量的状态
- * 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;
- /**
- * 父 View 已经检测出 View 所需要的精确大小, 这个时候 View 的最终大小就是 SpecSize 所指定的值, 它对应 LayoutParams 中的 match_parent 和具体数值这两种模式
- * 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;
- /**
- * 父 View 给子 VIew 提供一个最大可用的大小, 子 View 去自适应这个大小
- * 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 而是 LayoutParams, 在 View 测量的时候, LayoutParams 会和父 View 的 MeasureSpec 相结合被换算成 View 的 MeasureSpec, 进而决定 View 的大小
2.2 测量流程
测量流程简单来说就是调用 View 调用 measure()方法, View 的 measure()方法是一个 final 修饰的, 意味着我们不能够重写他, 最终它会调用 onMeasure()方法进行真正的测量, 测量原则就是循环遍历子类, 遍历每一个子节点对 View 进行测量, 直到最后一个 View 为止而这里 ViewGroup 和 View 的测量有点不同
View:View 在 onMeasure() 中会计算出自己的尺寸然后保存;
ViewGroup:ViewGroup 在 onMeasure()中会调用所有子 View 的 measure()让它们进行自我测量, 并根据子 View 计算出的期望尺寸来计算出它们的实际尺寸和位置然后保存同时, 它也会 根据子 View 的尺寸和位置来计算出自己的尺寸然后保存.
这里需要清楚的是 ViewGroup 类并没有实现 onMeasure, 因为都是在它的子类实现了, 不同的布局有不同的测量方法我们知道测量过程其实都是在 onMeasure 方法里面做的, 这里我们以 FrameLayout 为例, 来看下 FrameLayout 的 onMeasure 方法, 具体分析看注释
- //FrameLayout 的测量
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- ....
- int maxHeight = 0;
- int maxWidth = 0;
- int childState = 0;
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (mMeasureAllChildren || child.getVisibility() != GONE) {
- // 遍历自己的子 View, 只要不是 GONE 的都会参与测量, 基本思想就是父 View 把自己的 MeasureSpec
- // 传给子 View 结合子 View 自己的 LayoutParams 算出子 View 的 MeasureSpec, 然后继续往下传,
- // 传递叶子节点, 叶子节点没有子 View, 根据传下来的这个 MeasureSpec 测量自己就好了
- measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
- maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
- ....
- ....
- }
- }
- .....
- .....
- // 所有的孩子测量之后, 经过一系类的计算之后通过 setMeasuredDimension 设置自己的宽高,
- // 对于 FrameLayout 可能用最大的字 View 的大小, 对于 LinearLayout, 可能是高度的累加,
- // 具体测量的原理去看看源码总的来说, 父 View 是等所有的子 View 测量结束之后, 再来测量自己
- setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
- resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
- ....
- }
这里关键的方法是 measureChildWithMargins()方法, 我们再去看看 measureChildWithMargins()的源码
- ViewGroup#measureChildWithMargins():
- /**
- * Ask one of the children of this view to measure itself, taking into
- * account both the MeasureSpec requirements for this view and its padding
- * and margins. The child must have MarginLayoutParams The heavy lifting is
- * done in getChildMeasureSpec.
- * @param child The child to measure
- * @param parentWidthMeasureSpec The width requirements for this view
- * @param widthUsed Extra space that has been used up by the parent
- * horizontally (possibly by other children of the parent)
- * @param parentHeightMeasureSpec The height requirements for this view
- * @param heightUsed Extra space that has been used up by the parent
- * vertically (possibly by other children of the parent)
- */
- 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);
- }
这个方法主要做了:
1. 调用 getChildMeasureSpec()去获取子 View 的测量规格;
2. 调用 measure()方法进子 View 的测量
这里的 getChildMeasureSpec()非常重要, 因为里面定义了子 View 的测量规格, 也就是怎么通过父 View 的 MeasureSpec 和子类的 LayoutParams 来知道子 View 的大小
我们去 getChildMeasureSpec 看看源码
- /**
- * Does the hard part of measureChildren: figuring out the MeasureSpec to
- * pass to a particular child. This method figures out the right MeasureSpec
- * for one dimension (height or width) of one child view.
- *
- * The goal is to combine information from our MeasureSpec with the
- * LayoutParams of the child to get the best possible results. For example,
- * if the this view knows its size (because its MeasureSpec has a mode of
- * EXACTLY), and the child has indicated in its LayoutParams that it wants
- * to be the same size as the parent, the parent should ask the child to
- * layout given an exact size.
- *
- * @param spec The requirements for this view
- * @param padding The padding of this view for the current dimension and
- * margins, if applicable
- * @param childDimension How big the child wants to be in the current
- * dimension
- * @return a MeasureSpec integer for the child
- */
- public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
- int specMode = MeasureSpec.getMode(spec);// 父 View 的测量模式
- int specSize = MeasureSpec.getSize(spec);// 父 View 的测量大小
- 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);
- }
该方法用来获取子 View 的 MeasureSpec, 由参数我们就可以知道子 View 的 MeasureSpec 由父容器的 spec, 父容器中已占用的的空间大小 padding, 以及子 View 自身大小 childDimension 共同来决定的
通过上述方法, 我们可以总结出普通 View 的 MeasureSpec 的创建规则
当 View 采用固定宽高的时候, 不管父容器的 MeasureSpec 是什么, resultSize 都是指定的宽高, resultMode 都是 MeasureSpec.EXACTLY
当 View 的宽高是 match_parent, 当父容器是 MeasureSpec.EXACTLY, 则 View 也是 MeasureSpec.EXACTLY, 并且其大小就是父容器的剩余空间当父容器是 MeasureSpec.AT_MOST 则 View 也是 MeasureSpec.AT_MOST, 并且大小不会超过父容器的剩余空间
当 View 的宽高是 wrap_content 时, 不管父容器的模式是 MeasureSpec.EXACTLY 还是 MeasureSpec.AT_MOST,View 的模式总是 MeasureSpec.AT_MOST, 并且大小都不会超过父类的剩余空间
通过 getChildMeasureSpec()方法我们能够得到子 View 的测量规格, 然后 measureChildWithMargins ()会调用 measure()方法进子 View 的测量 (前面已经提到了) 前面我们已经说了, measure 方法是一个 final 定义的方法, 所以无法被重写, 但是真正测量的是在 onMeasure()方法中, 在源码中也可以得到验证, 这里我们直接来看 onMeasure()方法
- View#onMeasure():
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
通过 getDefaultSize()来获取直接测量的大小;
通过 setMeasuredDimension()来对测量的结果进行设置
我们来看 getDefaultSize()方法:
- View#getDefaultSize()
- /**
- * Utility to return a default size. Uses the supplied size if the
- * MeasureSpec imposed no constraints. Will get larger if allowed
- * by the MeasureSpec.
- *
- * @param size Default size for this view
- * @param measureSpec Constraints imposed by the parent
- * @return The size this view should be.
- */
- public static int getDefaultSize(int size, int measureSpec) {
- int result = size;
- int specMode = MeasureSpec.getMode(measureSpec);// 前面测量后返回的测量模式
- int specSize = MeasureSpec.getSize(measureSpec);// 前面测量后返回的测量大小
- switch (specMode) {
- //MeasureSpec.UNSPECIFIED 一般用来系统的内部测量流程
- case MeasureSpec.UNSPECIFIED:
- result = size;
- break;
- // 我们主要关注着两种情况, 它们返回的是 View 测量后的大小
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = specSize;
- break;
- }
- return result;
- }
通过 MeasureSpec 来获取最后的结果;
再看看 getDefaultSize 的第一个参数 getSuggestedMinimumWidth()方法:
- View#getSuggestedMinimumWidth()
- // 如果 View 没有设置背景, 那么返回 android:minWidth 这个属性的值, 这个值可以为 0
- // 如果 View 设置了背景, 那么返回 android:minWidth 和背景最小宽度两者中的最大值
- protected int getSuggestedMinimumWidth() {
- return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
- }
如果 View 没有设置背景, 那么返回 android:minWidth 这个属性的值, 这个值可以为 0 如果 View 设置了背景, 那么返回 android:minWidth 和背景最小宽度两者中的最大值
关于 getDefaultSize(int size, int measureSpec) 方法需要说明一下, 通过上面的描述我们知道 etDefaultSize()方法中 AT_MOST 与 EXACTLY 模式下, 返回的 都是 specSize, 这个 specSize 是父 View 当前可以使用的大小, 如果不处理, 那 wrap_content 就相当于 match_parent
如何处理?
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- Log.d(TAG, "widthMeasureSpec =" + widthMeasureSpec + "heightMeasureSpec =" + heightMeasureSpec);
- // 指定一组默认宽高, 至于具体的值是多少, 这就要看你希望在 wrap_cotent 模式下
- // 控件的大小应该设置多大了
- int mWidth = 200;
- int mHeight = 200;
- int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
- int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
- if (widthSpecMode == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {
- setMeasuredDimension(mWidth, mHeight);
- } else if (widthSpecMode == MeasureSpec.AT_MOST) {
- setMeasuredDimension(mWidth, heightSpecSize);
- } else if (heightSpecMode == MeasureSpec.AT_MOST) {
- setMeasuredDimension(widthSpecSize, mHeight);
- }
- }
注: 你可以自己尝试一下自定义一个 View, 然后不重写 onMeasure()方法, 你会发现只有设置 match_parent 和 wrap_content 效果是一样的, 事实上 TextViewImageView 等系统组件都在 wrap_content 上有自己的处理, 可以去翻一翻源码
这就是 View 的测量流程
3.View 的布局流程 Layout
performTraversals 方法执行完 mView.measure 计算出 mMeasuredXXX 后就开始执行 layout 函数来确定 View 具体放在哪个位置, 我们计算出来的 View 目前只知道 view 矩阵的大小, 具体这个矩阵放在哪里, 这就是 layout 的工作了 layout 的主要作用 : 根据子视图的大小以及布局参数将 View 树放到合适的位置上
既然是通过 mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); 那我们来看下 layout 函数做了什么, mView 肯定是个 ViewGroup, 不会是 View, 我们直接看下 ViewGroup 的 layout 函数
- @Override
- public final void layout(int l, int t, int r, int b) {
- if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
- if (mTransition != null) {
- mTransition.layoutChange(this);
- }
- super.layout(l, t, r, b);
- } else {
- // record the fact that we noop'd it; request layout when transition finishes
- mLayoutCalledWhileSuppressed = true;
- }
- }
代码可以看个大概, LayoutTransition 是用于处理 ViewGroup 增加和删除子视图的动画效果, 也就是说如果当前 ViewGroup 未添加 LayoutTransition 动画, 或者 LayoutTransition 动画此刻并未运行, 那么调用 super.layout(l, t, r, b), 继而调用到 ViewGroup 中的 onLayout 我们在源码中可以看到 ViewGroup 中的 onLayout()方法是一个抽象方法, 我们仍然以 FrameLayout 为例子, 去看看他的 onLayout 方法
- FrameLayout#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()方法给子类布局
- FrameLayout#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();
- // 循环遍历子 View 进行布局
- 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();
- int childLeft;
- int childTop;
- int gravity = lp.gravity;
- if (gravity == -1) {
- gravity = DEFAULT_CHILD_GRAVITY;
- }
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
- final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
- switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.CENTER_HORIZONTAL:
- childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
- lp.leftMargin - lp.rightMargin;
- break;
- case Gravity.RIGHT:
- if (!forceLeftGravity) {
- childLeft = parentRight - width - lp.rightMargin;
- break;
- }
- case Gravity.LEFT:
- default:
- childLeft = parentLeft + lp.leftMargin;
- }
- switch (verticalGravity) {
- case Gravity.TOP:
- childTop = parentTop + lp.topMargin;
- break;
- case Gravity.CENTER_VERTICAL:
- childTop = parentTop + (parentBottom - parentTop - height) / 2 +
- lp.topMargin - lp.bottomMargin;
- break;
- case Gravity.BOTTOM:
- childTop = parentBottom - height - lp.bottomMargin;
- break;
- default:
- childTop = parentTop + lp.topMargin;
- }
- child.layout(childLeft, childTop, childLeft + width, childTop + height);
- }
- }
- }
我们先来解释一下这个函数里的变量的含义
int left, int top, int right, int bottom: 描述的是当前视图的外边距, 即它与父窗口的边距
mPaddingLeft,mPaddingTop,mPaddingRight,mPaddingBottom: 描述的当前视图的内边距
通过这些参数, 我们就可以得到当前视图的子视图所能布局在的区域
接着, 该方法就会遍历它的每一个子 View, 并获取它的左上角的坐标位置: childLeft,childTop 这两个位置信息会根据 gravity 来进行计算 最后会调用子 View 的 layout()方法循环布局操作, 直到所有的布局都完成为止
4.View 的绘制流程 Draw
performTraversals 方法的下一步就是 mView.draw(canvas); 因为 View 的 draw 方法一般不去重写, 官网文档也建议不要去重写 draw 方法, 所以下一步执行就是 View.java 的 draw 方法, 我们来看下源码:
- 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;
- /*
- * 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)
- */
- // Step 1, draw the background, if needed
- int saveCount;
- if (!dirtyOpaque) {
- drawBackground(canvas);
- }
- // 检查是否可以跳过第 2 步和第 5 步, 也就是绘制变量, FADING_EDGE_HORIZONTAL == 1 表示处于水平
- // 滑动状态, 则需要绘制水平边框渐变效果, FADING_EDGE_VERTICAL == 1 表示处于垂直滑动状态, 则
- // 需要绘制垂直边框渐变效果
- // 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);
- if (debugDraw()) {
- debugDrawFocus(canvas);
- }
- // we're done...
- return;
- }
- /*
- * Here we do the full fledged routine...
- * (this is an uncommon case where speed matters less,
- * this is why we repeat some of the tests that have been
- * done above)
- */
- boolean drawTop = false;
- boolean drawBottom = false;
- boolean drawLeft = false;
- boolean drawRight = false;
- float topFadeStrength = 0.0f;
- float bottomFadeStrength = 0.0f;
- float leftFadeStrength = 0.0f;
- float rightFadeStrength = 0.0f;
- // Step 2, save the canvas' layers
- int paddingLeft = mPaddingLeft;
- final boolean offsetRequired = isPaddingOffsetRequired();
- if (offsetRequired) {
- paddingLeft += getLeftPaddingOffset();
- }
- int left = mScrollX + paddingLeft;
- int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
- int top = mScrollY + getFadeTop(offsetRequired);
- int bottom = top + getFadeHeight(offsetRequired);
- if (offsetRequired) {
- right += getRightPaddingOffset();
- bottom += getBottomPaddingOffset();
- }
- final ScrollabilityCache scrollabilityCache = mScrollCache;
- final float fadeHeight = scrollabilityCache.fadingEdgeLength;
- int length = (int) fadeHeight;
- // clip the fade length if top and bottom fades overlap
- // overlapping fades produce odd-looking artifacts
- if (verticalEdges && (top + length > bottom - length)) {
- length = (bottom - top) / 2;
- }
- // also clip horizontal fades if necessary
- if (horizontalEdges && (left + length > right - length)) {
- length = (right - left) / 2;
- }
- if (verticalEdges) {
- topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
- drawTop = topFadeStrength * fadeHeight > 1.0f;
- bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
- drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
- }
- if (horizontalEdges) {
- leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
- drawLeft = leftFadeStrength * fadeHeight > 1.0f;
- rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
- drawRight = rightFadeStrength * fadeHeight > 1.0f;
- }
- saveCount = canvas.getSaveCount();
- int solidColor = getSolidColor();
- if (solidColor == 0) {
- final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
- if (drawTop) {
- canvas.saveLayer(left, top, right, top + length, null, flags);
- }
- if (drawBottom) {
- canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
- }
- if (drawLeft) {
- canvas.saveLayer(left, top, left + length, bottom, null, flags);
- }
- if (drawRight) {
- canvas.saveLayer(right - length, top, right, bottom, null, flags);
- }
- } else {
- scrollabilityCache.setFadeColor(solidColor);
- }
- // Step 3, draw the content
- if (!dirtyOpaque) onDraw(canvas);
- // Step 4, draw the children
- dispatchDraw(canvas);
- // Step 5, draw the fade effect and restore layers
- final Paint p = scrollabilityCache.paint;
- final Matrix matrix = scrollabilityCache.matrix;
- final Shader fade = scrollabilityCache.shader;
- if (drawTop) {
- matrix.setScale(1, fadeHeight * topFadeStrength);
- matrix.postTranslate(left, top);
- fade.setLocalMatrix(matrix);
- p.setShader(fade);
- canvas.drawRect(left, top, right, top + length, p);
- }
- if (drawBottom) {
- matrix.setScale(1, fadeHeight * bottomFadeStrength);
- matrix.postRotate(180);
- matrix.postTranslate(left, bottom);
- fade.setLocalMatrix(matrix);
- p.setShader(fade);
- canvas.drawRect(left, bottom - length, right, bottom, p);
- }
- if (drawLeft) {
- matrix.setScale(1, fadeHeight * leftFadeStrength);
- matrix.postRotate(-90);
- matrix.postTranslate(left, top);
- fade.setLocalMatrix(matrix);
- p.setShader(fade);
- canvas.drawRect(left, top, left + length, bottom, p);
- }
- if (drawRight) {
- matrix.setScale(1, fadeHeight * rightFadeStrength);
- matrix.postRotate(90);
- matrix.postTranslate(right, top);
- fade.setLocalMatrix(matrix);
- p.setShader(fade);
- canvas.drawRect(right - length, top, right, bottom, p);
- }
- canvas.restoreToCount(saveCount);
- 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);
- if (debugDraw()) {
- debugDrawFocus(canvas);
- }
- }
注释写得比较清楚, 一共分成 6 步
1 第一步: 背景绘制
将背景绘制到对应的画布中, 这里就不贴代码了
2 第二步: 保存画布状态
保存当前画布的状态, 并且在当前画布创建额外的突出, 以便接下来可以绘制视图在滑动时的边框渐变效果
3 第三步, 对 View 的内容进行绘制
onDraw(canvas) 方法是 view 用来 draw 自己的, 具体如何绘制, 颜色线条什么样式就需要子 View 自己去实现, View.java 的 onDraw(canvas) 是空实现, ViewGroup 也没有实现, 每个 View 的内容是各不相同的, 所以需要由子类去实现具体逻辑 (我们自定义 View 写的 onDraw() 方法就是在这里进行绘制, 感兴趣的可以去看看源码)
4 第 4 步 对当前 View 的所有子 View 进行绘制
dispatchDraw(canvas) 方法是用来绘制子 View 的, View.java 的 dispatchDraw()方法是一个空方法, 因为 View 没有子 View, 不需要实现 dispatchDraw ()方法, ViewGroup 就不一样了, 它实现了 dispatchDraw ()方法
5 第五步 绘制滑动效果
绘制当前视图在滑动时的边框渐变效果并且恢复画布状态
6 第 6 步 对 View 的滚动条进行绘制
绘制当前视图的滚动条
这里面会检查是否可以跳过第 2 步和第 5 步, 也就是绘制变量, FADING_EDGE_HORIZONTAL == 1 表示处于水平滑动状态, 则需要绘制水平边框渐变效果, FADING_EDGE_VERTICAL == 1 表示处于垂直滑动状态, 则 需要绘制垂直边框渐变效果
一张图看下整个 draw 的递归流程
到这里 Draw 流程也就结束了
总结:
1. 绘制流程分为三步, Measure,Layout,Draw
2. 都是 ViewGroup 循环遍历子 View 进行操作, 直到最后一个子节点的 View
参考:
Android View 的绘制流程
Android 应用视图的载体 View
深入解析 Android 中 View 的工作原理
Android 应用层 View 绘制流程与源码分析
来源: https://juejin.im/entry/5a9d08c76fb9a028ca52770a