阅读其他文章+源码阅读的个人总结

Activity 中需要通过 setContentView 来设置要显示的视图,视图通常是一整个 View 树,承载这颗 View 树的 DecorView 就需要将这颗树绘制出来,就需要经历 Measure、Layout、Draw 这三个阶段;

Activity 并不直接与 View 系统进行交互,而是用过 Window 和 View 系统进行交互;

Window 中使用 ViewRootImpl 和 DecorView 与 View 进行交互;

下面是基本的流程图:

image-wpmc.png

Measure

Measure 是测量 View 尺寸大小的过程,只有知道了尺寸才能进行布局;

MeasureSpec

View 在 xml 中声明时需要带上自己的宽高属性,分为三种:wrap_contentmatch_parent 或指定值;

对于 match_parent 来说,需要知道父容器的尺寸才能知道自己的尺寸,如果父容器的宽是 100dp 那么该 View 的宽也是 100dp;

但是这个值可能来自于父容器的约束,也有可能来自于自身的约束,因此就需要使用 MeasureSpec 来区分这种区别;

MeasureSpec 类似一个工具类,负责计算并使用一个 int 类型(32位)来存储 View 的尺寸和 mode:

  • 前两位表示 mode:UNSPECIFIEDEXACTLYAT_MOST

  • 后30位表示 size

public static class MeasureSpec {
	private static final int MODE_SHIFT = 30;
    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);
        }
    }
}

三种测量模式:

mode

含义

UNSPECIFIED

ViewGroup 对于 View 没有任何限制,View 可以拿到任意想要的 size

EXACTLY

View 本身设定了确切大小的 size。 例如,View 的宽度设置为了 match_parent 或者具体的 dp 值; match_parent 即占满父容器,对于 View 来说也属于精准值

AT_MOST

size 是 View 能够占据的最大空间,View 的最终大小不能超出该范围。 对应 wrap_content,View 可以在父容器可以容纳的范围内申请空间

LayoutParams

LayoutParams 是 ViewGroup 的一个静态内部类;

包含了最基础的两个属性:

  • width:对应布局文件中的 layout_width

  • height:对应布局文件中的 layout_height

 public static class LayoutParams {
     @Deprecated
     public static final int FILL_PARENT = -1;
     public static final int MATCH_PARENT = -1;
     public static final int WRAP_CONTENT = -2;   
     
     public int width;
     public int height;
     
     // 通过读取布局文件 `attrs` 来解析宽高
     public LayoutParams(Context c, AttributeSet attrs) {
         TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
         setBaseAttributes(a,
                           R.styleable.ViewGroup_Layout_layout_width,
                           R.styleable.ViewGroup_Layout_layout_height);
         a.recycle();
     }
     // 外部直接指定宽高
     public LayoutParams(int width, int height) {
         this.width = width;
         this.height = height;
     }
 }

View 的 MeasureSpec 由 父容器的 MesureSpec(父容器尺寸)和 View 自身的 LayoutParams(自身尺寸)来共同决定:

  • View自身的尺寸需要受到父容器的尺寸影响,否则可能出现显示不全的问题(除非支持滚动)

ViewRootImpl & View

DecorView 是整个 View 树的根布局(继承于 FrameLayout),Activity 的 setContentView 就是向 DecorView 中执行 addView 操作;

既然是根布局,那么整个 View 树的测量过程需要从 DecorView 开始,从上到下从外到内进行;

DecorView 就是整个 View 树的最大可占据空间,其默认的宽高都是 match_parent 即占据整个屏幕空间;

ViewRootImpl 是整个 View 的顶层结构,但是并不是 View,作为 View 的管理者,是 WindowManagerGlobal 的委托者;

WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl

ViewRootImpl 的绘制流程入口是 requestLayout ,负责检查当前线程是否为UI线程,会调用到 scheduleTraversals

  • 向主线程 Handler 发送了一个异步 Message (开启了同步屏障)

  • 该 Messgae 是一个 TraversalRunnable,最终会执行到 doTraversal

 void scheduleTraversals() {
     if (!mTraversalScheduled) {
         mTraversalScheduled = true;
         // 同步屏障,让视图更新的 Message 尽快执行
         mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
         // 发送一个视图更新的消息
         mChoreographer.postCallback(
                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         notifyRendererOfFramePending();
         pokeDrawLockIfNeeded();
     }
 }
 ​
 // 上面发送的 mTraversalRunnable
 final class TraversalRunnable implements Runnable {
      @Override
      public void run() {
          doTraversal();
      }
  }


doTraversal 会关闭主线程Handler消息队列的同步屏障,并执行 performTraversals,该方法就是真正开始 View 的绘制流程的方法:(省略大部分代码,仅查看流程)

 private void performTraversals() {
     // 想要 Window 的尺寸
     int desiredWindowWidth;
     int desiredWindowHeight;
 ​
     if (mFirst) {
         ...
     } else {
         desiredWindowWidth = frame.width();
         desiredWindowHeight = frame.height();
     }
     if (!mStopped || mReportNextDraw) {
         if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()
             || dispatchApplyInsets || updatedConfiguration) {
             int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
                                                            lp.privateFlags);
             int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,
                                                             lp.privateFlags);
 ​
             // Ask host how big it wants to be
             // 执行测量 
             performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
 ​
             // 会对测量结果进行检查,如果存在问题会再次测量
             if (measureAgain) {
                 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
             }
             // 布局测量完毕
             layoutRequested = true;
         }
     } 
 ​
     // 准备布局
     final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
 ​
     if (didLayout) {
         performLayout(lp, mWidth, mHeight);
     }
 ​
     // 视图不可见
     if (!isViewVisible) {
         ...
     } else if (cancelAndRedraw) {
         // 重绘
         // Try again
         scheduleTraversals();
     } else {
         // 执行绘制操作
         if (!performDraw() && mActiveSurfaceSyncGroup != null) {
             mActiveSurfaceSyncGroup.markSyncReady();
         }
     }
 }

其中关于测量的是:

 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,lp.privateFlags);
 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,lp.privateFlags);
 // 执行测量 
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);


其中 getRootMeasureSpec 是根据窗口的尺寸以及 LayoutParams 生成了对应的 MeasureSpec

 private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
     int measureSpec;
     final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
             ? MATCH_PARENT : measurement;
     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;
 }

其中 performMesure 是开始测量的入口,其中 mView 是 DecorView,开始遍历整棵 View 树

 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);
     }
     mMeasuredWidth = mView.getMeasuredWidth();
     mMeasuredHeight = mView.getMeasuredHeight();
     mViewMeasureDeferred = false;
 }

View#measure 方法是 finla 修饰的方法,View 和 ViewGroup 的子类无法重写,该方法在执行完一些通用逻辑后,就会调用 onMeasure 方法;

onMeasure 方法就是进行自定义 View 的时候需要重写的方法之一,需要完成对 View 自身尺寸的测量(对于 ViewGroup 来说来需要测量所有子 View)

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
             getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
 }
 ​
 protected int getSuggestedMinimumWidth() {
     return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
 }
 ​
 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;
 }

测量完毕之后还需要将测量结果存储到自身,也就是调用 setMeasuredDimension 将测量结果传递过去;

  • 注意:测量结果 measureWidthmesaureHeight 不一定等于最终的宽高,需要在 layout 阶段才能确定下来;

  • 因此平时获取测量的宽高(测量结果)不一定是最终的宽高;

 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);
 }
 ​
 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
     mMeasuredWidth = measuredWidth;
     mMeasuredHeight = measuredHeight;
     mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
 }


ViewGroup

以 DecorView 为例,上面的 measure 开始测量的也是 DecorView,继承的 FrameLayout;

  • DecorView 增加了一些修正操作,当判断到 widthMode 和 heightMode 为 AT_MOST 时,就会尝试去将 mode 修正为 EXACTLY 并修改 size 大小,生成新的 widthMeasureSpecheightMeasureSpec,并调用 super.onMeasure 将实际的测量操作交由 FrameLayout 去完成

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     final int widthMode = getMode(widthMeasureSpec);
     final int heightMode = getMode(heightMeasureSpec);
     boolean fixedWidth = false;
     mApplyFloatingHorizontalInsets = false;
     if (widthMode == AT_MOST) {
        // ...
     }
     mApplyFloatingVerticalInsets = false;
     if (heightMode == AT_MOST) {
         // ...
     }
     
     // 具体的测量工作交给 FrameLayout
     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     int width = getMeasuredWidth();
     boolean measure = false;
     widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
     if (!fixedWidth && widthMode == AT_MOST) {
         // ...
     }
 }

继续看 FrameLayout 的 onMeasure 方法:

  • 对于 FrameLayout 的布局特性来说,其尺寸肯定是最大的子View决定的;

  • 除了这个外,还有 padding、minimumWidth、minimumHeight、foreground 等决定最终的测量尺寸大小;

  • 如果 FrameLayout 设置定了约束值(match_parent 或精准值),就需要按照该约束来执行了;

大致的测量步骤:

  • 对每个子View都测量得到其宽高,加上外边距,从中取最大值 maxHeightmaxWidth

  • 考虑了内边距 padding、最小尺寸 minimunXxx、前景drawable的最小尺寸

  • 考虑特殊情况,在后面阐述

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     int count = getChildCount();
     // FrameLayout 的 layout_width 或 layout_height 是否设置了 wrap_content
     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);
         // mMeasureAllChildren 默认为 false,不予理会
         // childView 只有不为 GONE 才需要 measure
         if (mMeasureAllChildren || child.getVisibility() != GONE) {
             // 完成 childView 的测量操作
             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);
             childState = combineMeasuredStates(childState, child.getMeasuredState());
             // 如果父容器是wrap_content,而子 View 是 match_parent,此时就需要确定最终大小
             // 用于后面逻辑
             if (measureMatchParentChildren) {
                 if (lp.width == LayoutParams.MATCH_PARENT ||
                         lp.height == LayoutParams.MATCH_PARENT) {
                     mMatchParentChildren.add(child);
                 }
             }
         }
     }
     // 考虑内边距以及 foreground 的内边距
     // Account for padding too
     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());
     }
     // 保存结果
     setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
             resolveSizeAndState(maxHeight, heightMeasureSpec,
                     childState << MEASURED_HEIGHT_STATE_SHIFT));
     ...
 }


当父容器为 wrap_content,某个子 View 为 match_parent,对于 FrameLayout 来说大小不能直接确定(子View要父容器的大小,而父容器又要子View的大小)

因此需要先进行第一步的测量,得到父容器的测量结果之后,再以该测量结果去测量子 View,得到子View的测量结果;

第二步就是执行这样的测量;

 count = mMatchParentChildren.size();
 if (count > 1) {
     for (int i = 0; i < count; i++) {
         final View child = mMatchParentChildren.get(i);
         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
         final int childWidthMeasureSpec;
         // 子 View 的宽度为 match_parent
         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);
         }
         // 以给定值重新测量
         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
 }

ParentMeasureSpec & LayoutParmas

对于 DecorView 来说,测量传入的 MeasureSpec 是通过测量屏幕宽高得到的;

对于子 View 来说,其 MeasureSpec 是父容器的 MeasureSpec 和 View 自己呃 LayoutParams 共同决定的

父容器测量子View时并不关心其具体类型,只是负责下发测量要求并要求接受测量结果;

  • 如果下一级是 ViewGroup, 则还需要继续分发下去先测量子View的大小;

  • 如果下一级是 View,则自己完成测量即可;

此时需要是父容器的 MeasureSpec;

该方法是生成对子View 的父容器MeasureSpec,需要先减去父容器的 padding 才考虑容纳子 View 的大小:

  • spec 是父容器 MeasureSpec

  • padding 是某个方向上的内边距

  • childDimension 是子 View 的 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:
         // 子 View 给了精确值
         if (childDimension >= 0) {
             // 子View给了精确值,如果大于size,则表现出来的是超出父容器
             resultSize = childDimension; 
             resultMode = MeasureSpec.EXACTLY;
         } else if (childDimension == LayoutParams.MATCH_PARENT) {
             // 子View的宽度与父容器相当
             resultSize = size;
             resultMode = MeasureSpec.EXACTLY;
         } else if (childDimension == LayoutParams.WRAP_CONTENT) {
             // 子View根据自身内容决定尺寸,禁止超过父容器大小
             resultSize = size;
             resultMode = MeasureSpec.AT_MOST;
         }
         break;
     // 父容器为 wrap_content
     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;
     // 父容器对子View没有约束
     case MeasureSpec.UNSPECIFIED:
         if (childDimension >= 0) {
             // Child wants a specific size... let them 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:

 // ViewGroup
 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);
 }


Layout

Layout 阶段就是View确定自己在屏幕上的位置

ViewRootImpl

Layout 开始的位置也是 ViewRootImpl 的 performTraversals 中执行完 performMeasure 开始的;

 // ViewRootImpl
 private void performTraversals() {
     ...
     if (didLayout) {
         // mWidth 和 mHeight 是测量过程中得到的屏幕宽高
         performLayout(lp, mWidth, mHeight);
         ...
     }
     ...
 }


performLayout 调用 DecorView 的 layout 方法,开始整颗 View 树的布局操作

 // ViewRootImpl
 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
         int desiredWindowHeight) {
     mScrollMayChange = true;
     mInLayout = true;
     final View host = mView; // DecorView
     if (host == null) {
         return;
     }
 ​
     try {
         // 调用 DecorView 的 layout
         host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
         mInLayout = false;
         ...
     } finally {
         ...
     }
     mInLayout = false;
 }

DecorView & ViewGroup

DecorView 并没有重写 layout 方法,最终调用到的是 ViewGroup 的(FrameLayout 也没有重写)layout 方法

只是对一些情况进行判断处理,最后还是调用其父类 View 的 layout 方法

 // ViewGroup
 @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;
     }
 }


View

View 通过 lefttoprightbottom 来标识自己在父容器中的位置(相对于父容器的坐标值);

  • setFramelayout 传入的值赋值给 View 的四个值

  • 至此 View 的宽高才真正确定下来,因为 getWidth()getHeight 是根据这四个值进行减法运算得到的;

其中会调用 onLayout:在View中是一个空实现,但是在 ViewGroup 就是用来布局其子View的;

 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);
     if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
         ...
         // 调用 onLayout  
         onLayout(changed, l, t, r, b);
         ...
         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);
             }
         }
     }
    ...
 }
 ​
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
 }


setFrame:将新的四个值分别赋值给View的对应字段,如果宽高发生变化,还会回调 sizeChange 方法;

 protected boolean setFrame(int left, int top, int right, int bottom) {
     boolean changed = false;
     ...
     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);
         }
         ...
     }
     return changed;
 }


ViewGroup#onLayout

大部分的 View 都不需要重写 onLayout 空实现;

但是 ViewGroup 将该方法变成抽象方法,规定其子类必须实现该方法来管理子View的摆放位置;

FrameLayout 和 LinearLayout 等容器都会通过该方法来实现不同的布局效果;

以 FrameLayout 为例:

  • 子View主要使用 layout_gravitylayout_margin 声明自己的位置;

  • 除此之外,FrameLayout 自身的 padding 也会影响到子View的布局位置;

  • 因此计算子View的位置时,需要考虑上面的因素并根据不同的情况计算出对应的 left、top、right、bottom

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

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();
            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);
        }
    }
}

Draw

绘制阶段:将整颗View树需要呈现出来的内容 “画” 到一块 Canvas 上;

ViewRootImpl 执行 performDraw,接着调用 draw 方法拿到 Surface,接着调用 drawSoft,从 Surface 中取得 Canvas后,调用 DecorView 的 draw 方法;

draw

View的 draw 方法是 final,无法重写,负责一些固定逻辑:

@CallSuper
public void draw(@NonNull Canvas canvas) {
	...
    /*
     * 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;
    }
  	....
}
  1. 绘制背景 drawBackground

     private void drawBackground(@NonNull 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);
         }
     }
  2. 保存当前图层信息(通常可以跳过)

  3. 绘制自身内容 onDraw,该方法是空实现,自定义View需要实现该方法;

     protected void onDraw(@NonNull Canvas canvas) {
     }
  4. 绘制子View的内容 dispatchDraw,自定义ViewGroup需要实现该方法来绘制子View的内容

     protected void dispatchDraw(@NonNull Canvas canvas) {
     }
  5. 绘制View的褪色边缘,类似于阴影效果。(可跳过)

  6. 绘制前景 onDrawForeground,比如滚动条等装饰;

     public void onDrawForeground(@NonNull 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);
         }
     }

  7. 绘制默认的焦点高亮drawDefaultFocusHighlight

     private void drawDefaultFocusHighlight(@NonNull Canvas canvas) {
         if (mDefaultFocusHighlight != null && isFocused()) {
             if (mDefaultFocusHighlightSizeChanged) {
                 mDefaultFocusHighlightSizeChanged = false;
                 final int l = mScrollX;
                 final int r = l + mRight - mLeft;
                 final int t = mScrollY;
                 final int b = t + mBottom - mTop;
                 mDefaultFocusHighlight.setBounds(l, t, r, b);
             }
             mDefaultFocusHighlight.draw(canvas);
         }
     }

相关方法

View#requestLayout

除了 ViewRootImpl 通过该方法来触发View树的测量、布局、绘制外,还有 View、ViewGroup 也提供了该方法:

  • 当View自己需要重新布局时,其父容器也是需要重新布局的,通过 mParent 层层向上传递,最终到达 DecorView

  • DecorView 在被 ViewRootImpl#setView 时,会设置其 mParent 为 ViewRootImpl,因此最终调用的是 ViewRootImpl#requestLayout

 // View
 public void requestLayout() {
     ...
     // 设置强制布局的标记位
     mPrivateFlags |= PFLAG_FORCE_LAYOUT;
     mPrivateFlags |= PFLAG_INVALIDATED;
     if (mParent != null && !mParent.isLayoutRequested()) {
         mParent.requestLayout();
     }
     if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
         mAttachInfo.mViewRequestingLayout = null;
     }
 }


ViewRootImpl#setView 中会给 DecorView 赋值 mParent:

 // ViewRootImpl
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
             int userId) {
     ...
     view.assignParent(this);
     ...
 }
 ​
 // View
 void assignParent(ViewParent parent) {
     if (mParent == null) {
         mParent = parent;
     } else if (parent == null) {
         mParent = null;
     } else {
         throw new RuntimeException("view " + this + " being added, but"
                 + " it already has a parent");
     }
 }


为了避免整颗 View 树的重绘,会给调用 requestLayout 的View及其所有父容器都会设置 PFLAG_FORCE_LAYOUTPFLAG_INVALIDATED 标记位,判断是否需要执行测量、布局流程;

对于 measure 方法:forceLayout 为 true 才会执行测量,在测量结束后还会设置 PFLAG_LAYOUT_REQUIRED 标志位

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
     ···
     final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
     ···
     if (forceLayout || needsLayout) {
         ···
         onMeasure(widthMeasureSpec, heightMeasureSpec);
         ···
         mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
     }
     ···
 }

对于 layout 方法:会判断 PFLAG_LAYOUT_REQUIRED 标志位,上面测量设置过后,这里就会执行布局流程;

 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);
 ​
     if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
         onLayout(changed, l, t, r, b);
 ​
         ···
     }
     ···
 }



对于
ViewRootImpl#draw 执行绘制流程的条件:

  • 只有 ViewRootImpl#invalidate 方法调用才会给 dirty 设置值;

  • 在布局过程中,如果某个View的尺寸发生改变,其 setFrame 方法会调用 invalidate,导致 dirty 不为空;

  • 对于尺寸没有变化的 View树,dirty.isEmpty() 返回的是 true,因此不会进行绘制操作

 if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
     // draw
 }

总结

调用了 reuqestLayout 方法会触发 ViewRootImpl#performTraversals 方法,依次执行 performMeasureperformLayoutperformDraw 流程;

调用者 View 及其父容器都会重新进行 measure 和 layout,不一定会重新绘制;

不是整颗 View 树都会被重新测量、布局、绘制,对于调用者 View 以及父容器会重新测量和布局,如果尺寸发生改变会触发重新绘制;

因此为了确保重绘操作执行,通常会在后面加上 invalidate


View#invalidate

用于让整个 View 失效,在某个时间点会重新调用其 onDraw

  • 需要在主线程调用,如果在子线程则需更换为 postInvalidate 方法

  • 使用 skipInvalidate 判断当前视图是否为可见,如果不可见同时没有动画执行,则跳过绘制;

  • 判断当前 View 的标志位是否需要重绘,如果需要则设置重绘相关的标志位

  • 通过父容器的 invalidateChild 将重绘事件传递给父容器;

 public void invalidate() {
     invalidate(true);
 }
 ​
 public void invalidate(boolean invalidateCache) {
     invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
 }
 ​
 void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
         boolean fullInvalidate) {
     if (mGhostView != null) {
         mGhostView.invalidate(true);
         return;
     }
     // 跳过不可见的View绘制
     if (skipInvalidate()) {
         return;
     }
     // Reset content capture caches
     // 标志位设置
     mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
     mContentCaptureSessionCached = false;
     if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
             || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
             || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
             || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
         if (fullInvalidate) {
             mLastIsOpaque = isOpaque();
             mPrivateFlags &= ~PFLAG_DRAWN;
         }
         mPrivateFlags |= PFLAG_DIRTY;
         if (invalidateCache) {
             mPrivateFlags |= PFLAG_INVALIDATED;
             mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
         }
         // Propagate the damage rectangle to the parent 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);
             p.invalidateChild(this, damage);
         }
         // Damage the entire projection receiver, if necessary.
         if (mBackground != null && mBackground.isProjected()) {
             final View receiver = getProjectionReceiver();
             if (receiver != null) {
                 receiver.damageInParent();
             }
         }
     }
 }

未开启硬件加速

未开启硬件加速会走到后面部分逻辑:


ViewGroup 的 invalidateChild 方法:

  • 通过循环不断层层向上传递重绘事件 invalidateChildInParent ,每次传递都会计算需要重绘的区间并集 dirty

  • 直到到达 DecorView,然后再到 ViewRootImpl 方法,最终调用到 invalidateRectOnScreen

 // ViewGroup
 public final void invalidateChild(View child, final Rect dirty) {
     final AttachInfo attachInfo = mAttachInfo;
     if (attachInfo != null && attachInfo.mHardwareAccelerated) {
         // HW accelerated fast path
         onDescendantInvalidated(child, child);
         return;
     }
     ViewParent parent = this;
     if (attachInfo != null) {
         ...
         do {
             View view = null;
             if (parent instanceof View) {
                 view = (View) parent;
             }
             ...
             parent = parent.invalidateChildInParent(location, dirty);
             ...
         } while (parent != null);
     }
 }

ViewRootImpl的 invalidateRectOnScreen 最终会调用 scheduleTraversals

 // ViewRootImpl
 private void invalidateRectOnScreen(Rect dirty) {
     if (DEBUG_DRAW) Log.v(mTag, "invalidateRectOnScreen: " + dirty);
     final Rect localDirty = mDirty;
     // Add the new dirty rect to the current one
     localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
     // Intersect with the bounds of the window to skip
     // updates that lie outside of the visible region
     final float appScale = mAttachInfo.mApplicationScale;
     final boolean intersected = localDirty.intersect(0, 0,
             (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
     if (!intersected) {
         localDirty.setEmpty();
     }
     if (!mWillDrawSoon && (intersected || mIsAnimating)) {
         scheduleTraversals();
     }
 }



ViewRootImpl#performTraversals 中:

  • ViewRootImpl#requestLayout 会设置标志位 mLayoutRequested = true;,该标志位会影响测量和布局的触发;

  • invalidate 并不会设置该标志位,因此不会触发对应的测量和布局过程,而是直接走绘制过程;

  • 触发测量 performMeasure 的条件:

     // layoutRequested 和 mLayoutRequested 相同
     boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
     if (layoutRequested) {
         if (!mFirst) {
             if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                 || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                 // 注意该字段,被设为true,导致下面的测量过程条件满足
                 windowSizeMayChange = true;
             }
         }
     }
     boolean windowShouldResize = layoutRequested && windowSizeMayChange
                 && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
                     || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
                             frame.width() < desiredWindowWidth && frame.width() != mWidth)
                     || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                             frame.height() < desiredWindowHeight && frame.height() != mHeight));
     ​
     if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
        || mForceNextWindowRelayout) {
     }
  • 触发布局 performLayout 的条件:

     final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);

对于 postInvalidate 实际上是往 ViewRootImpl 的 ViewRootHandler 发送了一个消息

public void postInvalidateDelayed(long delayMilliseconds) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
    Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
    mHandler.sendMessageDelayed(msg, delayMilliseconds);
}

private void handleMessageImpl(Message msg) {
    switch (msg.what) {
        case MSG_INVALIDATE:
            ((View) msg.obj).invalidate();
            break;
    }
}

其他的流程和 invaldate 一样;

开启硬件加速

如果开启了硬件加速,invalidate 的流程会有所不同,调用 invalidate 的 View 将事件传到 ViewGroup 会进行硬件加速的判断;

 // ViewGroup
 public final void invalidateChild(View child, final Rect dirty) {
     final AttachInfo attachInfo = mAttachInfo;
 ​
     if (attachInfo != null && attachInfo.mHardwareAccelerated) {
         // HW accelerated fast path
         onDescendantInvalidated(child, child);
         return;
     }
     ...
 }

调用了硬件加速相关的 onDescendantInvalidated,相同的一样会不断向上传递直到 ViewRootImpl#onDescendantInvalidated

 // ViewGroup
 @CallSuper
 public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
     ...
     if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
         mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
         // 这里将 PFLAG_DRAWING_CACHE_VALID 标志位置0,记住这里,后面有用到
         mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
     }
     if (mParent != null) {
         mParent.onDescendantInvalidated(this, target);
     }
 }

需要注意的是,调用了 invalidate 的 View 会设置 PFLAG_INVALIDATED 标志位,而向上传递到达的 View 并不会设置改标志位

ViewRootImpl 的 onDescendantInvalidated 最终也会调用到 performTraversals

 // ViewRootImpl
 public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
     // TODO: Re-enable after camera is fixed or consider targetSdk checking this
     // checkThread();
     if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
         mIsAnimating = true;
     }
     invalidate();
 }
 ​
 void invalidate() {
     mDirty.set(0, 0, mWidth, mHeight);
     if (!mWillDrawSoon) {
         scheduleTraversals();
     }
 }

但是开启了硬件加速的绘制流程并不会走 drawSoft,而是走 ThreadedRenderer#draw:(View参数是DecorView)

 // ThreadedRenderer
 void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
     ...
     updateRootDisplayList(view, callbacks);
     ...
 }
 ​
 private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
     ...
     updateViewTreeDisplayList(view);
     ...
 }
 ​
 private void updateViewTreeDisplayList(View view) {
     view.mPrivateFlags |= View.PFLAG_DRAWN;
     // 在调用 invalidate 的View 和顶部View之间所有View都是false
     view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
             == View.PFLAG_INVALIDATED; 
     view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
     view.updateDisplayListIfDirty();
     view.mRecreateDisplayList = false;
 }


继续跟进 View#updateDisplayListIfDirty

  • 第一个if分支的mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 是成立的,因为 onDescendantInvalidated 调用过程中会将该标志位设置为0

  • 进入第一个if分支后的条件也是成立的:

    • hasDisplayList 是判断 renderNode 是否有效,在首次绘制会返回true;

    • mRecreateDisplayList 是没有设置 PFLAG_INVALIDATED 标志位时为 false,即不需要重新创建 DisplayList

    因此会进入 dispatchGetDisplayList 方法,继续查找对应的子View,而不会走后面的重绘逻辑

 // View
 public RenderNode updateDisplayListIfDirty() {
     final RenderNode renderNode = mRenderNode;
     ...
     // 会进入该分支
     if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
             || !renderNode.hasDisplayList()
             || (mRecreateDisplayList)) {
         
         // hasDisplayList 是判断renderNode 是否有效,在首次绘制会返回true
         // 调用这个: nIsValid(mNativeRenderNode);
         if (renderNode.hasDisplayList()
                 && !mRecreateDisplayList) {
             mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
             mPrivateFlags &= ~PFLAG_DIRTY_MASK;
             dispatchGetDisplayList();
             return renderNode; // no work needed
         }
         ...
     return renderNode;
 }

View#dispatchGetDisplayList 是空实现,实现了该方法的是 ViewGroup:

  • 会遍历其所有的子View,对于可见或执行动画的子View会调用 recreateChildDisplayList

  • 会重新调用到 View#updateDisplayListIfDirty,这是一个递归过程,遍历当前所有子 View

 // ViewGroup
 protected void dispatchGetDisplayList() {
     final int count = mChildrenCount;
     final View[] children = mChildren;
     for (int i = 0; i < count; i++) {
         final View child = children[i];
         if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
             recreateChildDisplayList(child);
         }
     }
     ....
 }
 ​
 private void recreateChildDisplayList(View child) {
     // 设置了 PFLAG_INVALIDATED 会为 true
     child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
     child.mPrivateFlags &= ~PFLAG_INVALIDATED;
     child.updateDisplayListIfDirty();
     child.mRecreateDisplayList = false;
 }


对于设置了 PFLAG_INVALIDATED 标志位的 View,其 mRecreateDisplayList 为 true,因此在 View#updateDisplayListIfDirty 方法中会执行后续的重绘逻辑,该重绘会执行到 draw 方法:

  • 对于 View 来说,只会重绘本身;

  • 对于 ViewGroup 来说,还会调用 dispatchDraw 绘制其所有View

不管怎么样,还是会遍历完整颗 View 树,只是针对调用了 invalidate 的View 进行重绘罢了;

不过对比关闭硬件加速的 drawSoft会重绘整颗 View 树,硬件加速的的重绘效率会高上许多

总结

对于未开启硬件加速的 invalidate,走的是 drawSoft ,会重绘整颗 View 树;

对于开启硬件加速的 invalidate,虽然也会触发 ViewRootImpl#performTraversals ,但走的是 ThreadedRendered 的重绘,会遍历整颗 View 树,找到设置了标志位 PFLAG_INVALIDATED 的子View,对该View执行重绘操作,而不会绘制其他View;

相关问题

LayoutInflater:LayoutParams在什么时候生成?

xml声明布局的属性是怎么转换为 LayoutParams 以及对应 View 对象呢?

在 DecorView 生成内部布局时,使用过 LayoutInflater 执行的:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    ...
    final View root = inflater.inflate(layoutResource, null);
    ...
}


内部会调用该方法:

  • 首先会尝试从预编译类加载资源(略过)

  • 拿到一个可以读取指定布局文件的 XmlResourceParser

  • 通过 inflate 方法将xml的布局文件转化为 View 树

 // LayoutInflater
 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
     final Resources res = getContext().getResources();
     ...
     // 尝试从预编译的资源中加载    
     View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
     if (view != null) {
         return view;
     }
     XmlResourceParser parser = res.getLayout(resource);
     try {
         return inflate(parser, root, attachToRoot);
     } finally {
         parser.close();
     }
 }

inflater 的方法大致可以总结为:

  • 通过 createView 反射 或 LayoutInflater.Factory 创建对应的View对象

  • 通过 rInflateChildren 递归生成嵌套的子View对象

 // LayoutInflater
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
     synchronized (mConstructorArgs) {
         // xml 声明的属性
         final AttributeSet attrs = Xml.asAttributeSet(parser);
         View result = root; 
         ...
         try {
             // 移动到根节点部分
             advanceToRootNode(parser);
             final String name = parser.getName();
             // 根节点是 merge 标签
             if (TAG_MERGE.equals(name)) {
                 if (root == null || !attachToRoot) {
                     throw new InflateException("<merge /> can be used only with a valid "
                             + "ViewGroup root and attachToRoot=true");
                 }
                 rInflate(parser, root, inflaterContext, attrs, false);
             } else {
                 // 根节点对应的View
                 final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                 ...
                 ViewGroup.LayoutParams params = null;
                 if (root != null) {
                   
                     // 根据当前 temp 生成对应的 LayoutParams
                     params = root.generateLayoutParams(attrs);
                     if (!attachToRoot) {
                         // 添加到 root 是使用 addView
                         // 这里不用添加到 root
                         temp.setLayoutParams(params);
                     }
                 }
                 // 递归创建其所有的子View
                 rInflateChildren(parser, temp, attrs, true);
              
                 // 添加到 root
                 if (root != null && attachToRoot) {
                     root.addView(temp, params);
                 }
                 // Decide whether to return the root that was passed in or the
                 // top view found in xml.
                 if (root == null || !attachToRoot) {
                     result = temp;
                 }
             }
         } 
         return result;
     }
 }



rInflate
是一个递归生成嵌套View层级的方法;

 // LayoutInflater
 void rInflate(XmlPullParser parser, View parent, Context context,
         AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
     final int depth = parser.getDepth();
     int type;
     boolean pendingRequestFocus = false;
     // 相同层级的子View添加到 parent 中
     while (((type = parser.next()) != XmlPullParser.END_TAG ||
             parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
         if (type != XmlPullParser.START_TAG) {
             continue;
         }
         final String name = parser.getName();
         if (TAG_REQUEST_FOCUS.equals(name)) {
             pendingRequestFocus = true;
             consumeChildElements(parser);
         } else if (TAG_TAG.equals(name)) {
             parseViewTag(parser, parent, attrs);
         } else if (TAG_INCLUDE.equals(name)) {
             if (parser.getDepth() == 0) {
                 throw new InflateException("<include /> cannot be the root element");
             }
             // 解析 include 标签
             parseInclude(parser, context, parent, attrs);
         } else if (TAG_MERGE.equals(name)) {
             // 嵌套中遇到 merge 标签是异常情况
             throw new InflateException("<merge /> must be the root element");
         } else {
             // 相同层级的 view
             final View view = createViewFromTag(parent, name, context, attrs);
             final ViewGroup viewGroup = (ViewGroup) parent;
             final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
             // 递归嵌套生成
             rInflateChildren(parser, view, attrs, true);
             viewGroup.addView(view, params);
         }
     }
     if (pendingRequestFocus) {
         parent.restoreDefaultFocus();
     }
     if (finishInflate) {
         parent.onFinishInflate();
     }
 }


从上面源码阅读中,可能会产生疑惑,AttributeSet 似乎从始至终都是同一个对象啊,但是每次 generateLayoutParams 都是用同一个对象,这似乎每个View生成的 generateLayoutParams 都是同一个东西?那岂不是这些View的属性都是相同的?

  • 首先需要知道获取到的是 parser,只是实现了 AttributeSet 接口,并没有生成别的东西;

     public static AttributeSet asAttributeSet(XmlPullParser parser) {
         return (parser instanceof AttributeSet)
             ? (AttributeSet) parser
             : new XmlPullAttributes(parser);
     }
  • XmlPullParser 会不断通过 next 来走到下一个节点的,对于当前节点,generateLayoutParams 以及实例化 View 对象时传递的 attr 都是在当前节点位置,因此拿到的都是当前节点的属性;

  • 这个读取属性是跟着递归 View 层级一起的,保证解析某个View时拿到的属性也是这个 View 的属性;