千锋教育-做有情怀、有良心、有品质的职业教育机构

400-811-9990
手机站
千锋教育

千锋学习站 | 随时随地免费学

千锋教育

扫一扫进入千锋手机站

领取全套视频
千锋教育

关注千锋学习站小程序
随时随地免费学习课程

上海
  • 北京
  • 郑州
  • 武汉
  • 成都
  • 西安
  • 沈阳
  • 广州
  • 南京
  • 深圳
  • 大连
  • 青岛
  • 杭州
  • 重庆
当前位置:济南千锋IT培训  >  技术干货  >  Android源码分析requestLayout和invalidate的区别?

Android源码分析requestLayout和invalidate的区别?

来源:千锋教育
发布人:xqq
时间: 2023-10-15 12:20:52

一、requestLayout和invalidate的区别

requestLayout() 和 invalidate() 的区别在于它们作用的范围不同。requestLayout() 用于通知 View 进行重新布局,即测量、布局和绘制三个步骤都会重新进行;而 invalidate() 用于通知 View 进行重绘,仅仅是在原有的尺寸和位置上重新绘制 View,不会重新进行测量和布局。

requestLayout:调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会重新从上往下进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。因此,当只需要进行重绘时可以使用 invalidate 方法,如果需要重新测量和布局则可以使用 requestLayout 方法,而 requestLayout 方法不一定会重绘,因此如果要进行重绘可以再手动调用 invalidate 方法。invalidate:调用 View.invalidate() 方法后会逐级往上调用父 View 的相关方法,最终在 Choreographer 的控制下调用 ViewRootImpl.performTraversals() 方法。只有满足 mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null … 等条件才会执行 measure 和 layout 流程,否则只执行 draw 流程,draw 流程的执行过程与是否开启硬件加速有关:关闭硬件加速则从 DecorView 开始往下的所有子 View 都会被重新绘制。开启硬件加速则只有调用 invalidate 方法的 View 才会重新绘制。

二、requestLayout方法介绍

1、View.requestLayout

Java复制代码public void requestLayout() {    if (mMeasureCache != null) mMeasureCache.clear();    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {        // 如果处于 Layout 则将该请求加入 ViewRootImpl 中的任务队列中        ViewRootImpl viewRoot = getViewRootImpl();        if (viewRoot != null && viewRoot.isInLayout()) {            if (!viewRoot.requestLayoutDuringLayout(this)) {                return;            }        }        mAttachInfo.mViewRequestingLayout = this;    }    // 添加标志位    mPrivateFlags |= PFLAG_FORCE_LAYOUT;    mPrivateFlags |= PFLAG_INVALIDATED;    if (mParent != null && !mParent.isLayoutRequested()) {        mParent.requestLayout();    }    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {        mAttachInfo.mViewRequestingLayout = null;    }}// ViewRootImplboolean requestLayoutDuringLayout(final View view) {    if (!mLayoutRequesters.contains(view)) {        mLayoutRequesters.add(view);    }    // ...}

如果此时处于 Layout 则将该请求加入 ViewRootImpl 中的任务队列中,否则向上调用父 View 的 requestLayout 方法,直到 ViewRootImpl 中:

Java复制代码public void requestLayout() {    if (!mHandlingLayoutInLayoutRequest) {        checkThread();        mLayoutRequested = true;        scheduleTraversals();    }}

ViewRootImpl.requestLayout 方法在 check 了线程后将 mLayoutRequested 置为 true 且调用 scheduleTraversals 方法,于是在 Vsync 信号到来后会调用 performTraversals 方法。由于 mLayoutRequested == true,因此会依次执行 performMeasure, performLayout 以及 performDraw 方法开始 View 的绘制流程。

2、绘制过程

measure:

接下来看看 View.requestLayout 方法对整个 View 树的影响。首先看一下 View.measure 方法:

Java复制代码public final void measure(int widthMeasureSpec, int heightMeasureSpec) {    // ...    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;    final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec;    final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;    final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);    final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);    if (forceLayout || needsLayout) {        // ...        onMeasure(widthMeasureSpec, heightMeasureSpec);        // 设置 PFLAG_LAYOUT_REQUIRED 标志位        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;    }}

在 View.requestLayout 方法中已经看到给当前 View 及其父 View 都添加了 PFLAG_FORCE_LAYOUT 标志位,因此其 forceLayout == ture,即会执行 onMeasure 方法测量。而对于未设置 PFLAG_FORCE_LAYOUT 标志位的 View 则需要判断其尺寸是否发生改变才会决定调用 onMeasure 与否。我们看到调用 onMeasure 后又设置了 PFLAG_LAYOUT_REQUIRED 标志位。

layout:

接着看 View.layout 方法:

Java复制代码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);        // ...    }    // ...}

由于调用 onMeasure 后设置了 PFLAG_LAYOUT_REQUIRED 标志位,因此也会跟着执行 onLayout 方法。另外看一下 setOpticalFrame 和 setFrame 方法,其中 setOpticalFrame 方法中最终也会调用到 setFrame 方法:

Java复制代码protected boolean setFrame(int left, int 较好, int right, int bottom) {    boolean changed = false;    if (mLeft != left || mRight != right || mTop != 较好 || mBottom != bottom) {        int oldWidth = mRight - mLeft;        int oldHeight = mBottom - mTop;        int newWidth = right - left;        int newHeight = bottom - 较好;        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);        invalidate(sizeChanged);        // ...    }}

因此可以看到当 View 四个顶点发生变化时也会调用 onLayout 方法,且会调用 View.invalidate 方法,并将 View 的宽高是否发生变化传给 invalidateCache 参数。

draw:

ViewRootImpl.performDraw 会调用到 ViewRootImpl.draw 方法:

Java复制代码private boolean draw(boolean fullRedrawNeeded) {    final Rect dirty = mDirty;    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {        // 硬件绘制/软件绘制    }}

dirty 是脏区,在 ViewRootImpl.invalidate 方法中会调用 mDirty.set() 方法为其设置边界值,如果上面 View 的顶点没有发生变化则不会调用 invalidate 方法,则 dirty.isEmpty() 返回 true,因此整个 View 树都不会重绘。

3、小结

调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会从上往下重新进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。

三、invalidate方法介绍

1、View.invalidate

先看一下 invalidate 这个方法:

Java复制代码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 (skipInvalidate()) { // 判断是否需要跳过 invalidate        return;    }    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(); // 重新设置 Opaque            mPrivateFlags &= ~PFLAG_DRAWN; // 移除 PFLAG_DRAWN 标志位        }        mPrivateFlags |= PFLAG_DIRTY; // 设置 PFLAG_DIRTY 脏区标志位        if (invalidateCache) {            mPrivateFlags |= PFLAG_INVALIDATED; // 设置 PFLAG_INVALIDATED 标志位            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; // 移除 PFLAG_DRAWING_CACHE_VALID 标志位        }        final AttachInfo ai = mAttachInfo;        final ViewParent p = mParent;        if (p != null && ai != null && l < r && t < b) {            // damage 表示要重绘的脏区            final Rect damage = ai.mTmpInvalRect;            damage.set(l, t, r, b);            p.invalidateChild(this, damage);        }        // ...    }}private boolean skipInvalidate() {    return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&            (!(mParent instanceof ViewGroup) || !((ViewGroup) mParent).isViewTransitioning(this));}

首先会通过 skipInvalidate 方法判断是否要跳过 invalidate 过程,如果同时满足以下条件则跳过:

View 不可见当前没有运行动画父 View 不是 ViewGroup 类型或者父 ViewGoup 不处于过渡态

接下来再判断是否需要重绘,如果满足以下任意一个条件则进行重绘:

View 已经绘制完成且具有边界invalidateCache == true 且设置了 PFLAG_DRAWING_CACHE_VALID 标志位,即绘制缓存可用没有设置 PFLAG_INVALIDATED 标志位,即没有被重绘过fullInvalidate == true 且在 透明 和 不透明 之间发生了变化

在处理了一些标志位的逻辑后调用了父 View 的 invalidateChild 方法并将要重绘的区域 damage 传给父 View。于是接着看 ViewGroup.invalidateChild 方法:

Java复制代码public final void invalidateChild(View child, final Rect dirty) {    final AttachInfo attachInfo = mAttachInfo;    if (attachInfo != null && attachInfo.mHardwareAccelerated) {        // 开启了硬件加速        onDescendantInvalidated(child, child);        return;    }    // 未开启硬件加速    ViewParent parent = this;    if (attachInfo != null) {        // ...        do {            // ...            parent = parent.invalidateChildInParent(location, dirty);            // 重新设置脏区            // ...        } while (parent != null);    }}

可以看到这里会根据是否开启了硬件加速而走不同的逻辑。

2、小结

调用 View.invalidate() 方法后会逐级往上调用父 View 的相关方法,最终在 Choreographer 的控制下调用 ViewRootImpl.performTraversals() 方法。由于 mLayoutRequested == false,因此只有满足 mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null … 等条件才会执行 measure 和 layout 流程,否则只执行 draw 流程,draw 流程的执行过程与是否开启硬件加速有关:

关闭硬件加速则从 DecorView 开始往下的所有子 View 都会被重新绘制。开启硬件加速则只有调用 invalidate 方法的 View 才会重新绘制。

View 在绘制后会设置 PFLAG_DRAWN 标志位。

延伸阅读1:Android中的View类简介

Android中的View类代表用户界面中基本的构建块。一个View在屏幕中占据一个矩形区域、并且负责绘制和事件处理。View是所有widgets的基础类,widgets是我们通常用于创建和用户交互的组件,比如按钮、文本输入框等等。子类ViewGroup是所有布局(layout)的基础类。layout是一个不看见的容器,里面堆放着其他的view或者ViewGroup,并且设置他们的布局属性。

声明:本站稿件版权均属千锋教育所有,未经许可不得擅自转载。

猜你喜欢LIKE

位图和矢量图的区别?

2023-10-15

什么是聚类分析?

2023-10-15

Linux中的软链接、硬链接:都用在哪些场合?

2023-10-15

最新文章NEW

开发者选项打开有什么坏处?

2023-10-15

固态硬盘和机械硬盘的区别?

2023-10-15

什么是云数据库?

2023-10-15

相关推荐HOT

更多>>

快速通道 更多>>

最新开班信息 更多>>

网友热搜 更多>>