美文网首页Android 视图模块
Android 自定义控件 requestLayout / in

Android 自定义控件 requestLayout / in

作者: 科技猿人 | 来源:发表于2021-02-05 16:02 被阅读0次

    Read The Fucking Source Code

    引言

    Android自定义控件涉及View的绘制分发流程

    源码版本(Android Q — API 29)

    本文涉及Android绘制流程

    Android 绘制流程

    1. requestLayout

    1.1 View的 requestLayout 过程

    1.2 requestLayout自底向上

    1.2.1 我们来看View中的requestLayout()方法。
    public void requestLayout() {
            if (mMeasureCache != null) mMeasureCache.clear();
    
            //View树正在进行布局流程
            if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
                // Only trigger request-during-layout logic if this is the view requesting it,
                // not the views in its parent hierarchy
                ViewRootImpl viewRoot = getViewRootImpl();
                if (viewRoot != null && viewRoot.isInLayout()) {
                    //ViewRootImpl是否会截获处理布局(已经在不居过程中),后面会有说明
                    if (!viewRoot.requestLayoutDuringLayout(this)) {
                        return;
                    }
                }
                mAttachInfo.mViewRequestingLayout = this;
            }
    
            //View设置标记位,需要重新布局
            mPrivateFlags |= PFLAG_FORCE_LAYOUT;
            mPrivateFlags |= PFLAG_INVALIDATED;
    
            //向父容器请求布局,即调用父容器的requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位
            if (mParent != null && !mParent.isLayoutRequested()) {
                mParent.requestLayout();
            }
            if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
                mAttachInfo.mViewRequestingLayout = null;
            }
        }
    
    1.2.2 我们来看ViewRootImpl中的requestLayoutDuringLayout()方法。
    //返回值决定请求布局发起方是否进行递归布局申请,fase:中断布局请求;ture:布局请求继续执行。
    boolean requestLayoutDuringLayout(final View view) {
            if (view.mParent == null || view.mAttachInfo == null) {
                // Would not normally trigger another layout, so just let it pass through as usual
                return true;
            }
            //正在布局的视图包含请求view,则添加
            if (!mLayoutRequesters.contains(view)) {
                mLayoutRequesters.add(view);
            }
            //是否允许在不居中申请布局请求
            if (!mHandlingLayoutInLayoutRequest) {
                // Let the request proceed normally; it will be processed in a second layout pass
                // if necessary
                return true;
            } else {
                // Don't let the request proceed during the second layout pass.
                // It will post to the next frame instead.
                return false;
            }
        }
    
    1.2.3 ViewGroup中没有复写requestLayout方法,所以ViewGroup也是调用和View同样的方法。
    1.2.4 我们来看ViewRootImpl中的requestLayout()方法。
    public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                //线程检查
                checkThread();
                mLayoutRequested = true;
                //请求注册Vsync信号触发绘制
                scheduleTraversals();
            }
        }
    
    1.2.5 requestLayout()小结

     requestLayout事件层层向上传递,直到DecorView(即根View),又会传递给ViewRootImpl(ViewRootImpl接管了DecorView的ViewParent功能),也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,作为View的外交大管家,只有ViewRootImpl能够处理requestLayout事件。

    2. invalidate

    2.1 View的 invalidate 过程

    2.2 invalidate自底向上

    2.2.1 我们来看View中的invalidate()方法。
    public void invalidate() {
            invalidate(true);
        }
    
    public void invalidate(boolean invalidateCache) {
            invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
        }
    
    2.2.2 我们来看View中的invalidateInternal()方法。
    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
                boolean fullInvalidate) {
            //代码省略……
    
            //这里判断该子View是否可见或者是否处于动画中
            if (skipInvalidate()) {
                return;
            }
    
            // Reset content capture caches
            mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
            mCachedContentCaptureSession = null;
    
            //根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
            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;
                }
    
                //设置PFLAG_DIRTY标记位(增加脏区标记位)
                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);
                }
    
                //代码省略……
            }
        }
    
    2.2.3 我们来看ViewGroup中的invalidateChild()方法。
    @Deprecated(请注意这个废弃的标识,这个方法以后可能要废弃)
        @Override
        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;
            }
    
            //代码省略……
    
            //如果硬件加速关闭,才会之前版本的递归遍历过程(此过程不讲了,网上大部分的博客还停留在这个版本阶段)
            parent = parent.invalidateChildInParent(location, dirty);
    
           //代码省略……
    
        }
    
    2.2.4 我们来看ViewGroup中的onDescendantInvalidated()方法。
    //经过针对硬件加速优化后的代码,就显得清新脱俗了
    public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
            /*
             * HW-only, Rect-ignoring damage codepath
             *
             * We don't deal with rectangles here, since RenderThread native code computes damage for
             * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
             */
    
            // if set, combine the animation flag into the parent
            mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);
    
            if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
                // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
                // optimization in provides in a DisplayList world.
                mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
    
                // simplified invalidateChildInParent behavior: clear cache validity to be safe...
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }
    
            // ... and mark inval if in software layer that needs to repaint (hw handled in native)
            if (mLayerType == LAYER_TYPE_SOFTWARE) {
                // Layered parents should be invalidated. Escalate to a full invalidate (and note that
                // we do this after consuming any relevant flags from the originating descendant)
                mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
                target = this;
            }
    
            //递归调用父类的onDescendantInvalidated,最终会调用到ViewRootImpl中的这个方法。
            if (mParent != null) {
                mParent.onDescendantInvalidated(this, target);
            }
        }
    
    2.2.5 我们来看ViewRootImpl中的onDescendantInvalidated()方法。
    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();
        }
    
    2.2.6 我们来看ViewRootImpl中的invalidate()方法。
    void invalidate() {
            mDirty.set(0, 0, mWidth, mHeight);
            if (!mWillDrawSoon) {
                //绘制请求,注册Vsync信号,等待上报绘制刷新。
                scheduleTraversals();
            }
        }
    
    2.2.7 invalidate小结

     当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。

    2.2.8 postInvalidate简介

     postInvalidate和invalidate的作用是一样的,唯一的区别是,postInvalidate可以在子线程中调用请求刷新UI。为什么呢?因为请求重新布局和绘制,最终都会在ViewRootImpl中处理,而ViewRootImpl会在请求方法中,进行线程检查(是否是UI线程)。当子view中有请求绘制的需求怎么办,那么就用postInvalidate。其实postInvalidate的功能就是 线程切换 + invalidate调用。

    3 问题思考

    requestLayout 和 invaldate 有什么区别?

    • requestLayout 和 invalidate 都会触发整个绘制流程。但是在 measure 和 layout 过程中,只会对 flag 设置为 FORCE_LAYOUT 的情况进行重新测量和布局,而 draw 只会重绘 flag 为 dirty 的区域。
    • requestLayout 是用来设置 FORCE_LAYOUT 标志,invalidate 用来设置 dirty 标志。所以 requestLayout 只会触发 measure 和 layout,invalidate 只会触发 draw。
    • 所以一般都是组合使用。比如:只要刷新的时候就调用 invalidate,需要重新 measure 就调用 requestLayout,后面再跟个 invalidate(为了保证重绘)

    小编的扩展链接

    《Android 视图模块 全家桶》

    相关文章

      网友评论

        本文标题:Android 自定义控件 requestLayout / in

        本文链接:https://www.haomeiwen.com/subject/gvmwtltx.html