View源码分析之invalidate与requestLayout

invalidate()

当一个View调用了invalidate方法的话,然后就很容易走到了这个方法

View.invalidateInternal()

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {

        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)) {

           ...

            final ViewParent p = mParent; //首先获取当前View 的父容器
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                //然后调用了ViewGroup的invalidateChild方法
                p.invalidateChild(this, damage);
            }

           ...
        }
    }

接下来我们就走到了ViewGroup的的invalidateChild()方法中了

ViewGroup.invalidateChild()

 @Deprecated
    @Override
    public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;

             ...

            //这个while循环的主要作用是一步步返回到最顶层的ViewGroup

            do {

               ...

                //这个方法我们就可以发现了刚才我说的

                parent = parent.invalidateChildInParent(location, dirty);


                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }
    }

最后一直往上寻找他们的根,最后一直会找到一个ViewRootImpl类,这个类很重要,我会在后面的文章中详细的讲解,今天主要的任务是看这个invalidate方法,下面我们继续看:

ViewRootImpl.invalidateChildInParent

@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {

    //我们不能更新ui在非主线程中,就是因为有这个  checkThread()的方法存在,

        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

        if (dirty == null) {
            invalidate();//这就是更新界面的入口了
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        if (mCurScrollY != 0 || mTranslator != null) {
            mTempRect.set(dirty);
            dirty = mTempRect;
            if (mCurScrollY != 0) {
                dirty.offset(0, -mCurScrollY);
            }
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }

        invalidateRectOnScreen(dirty);

        return null;
    }

之所以将这个方法提取出来,如果我们稍微了解过View的绘制流程的同学对这个 scheduleTraversals()比较敏感.

 void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }

继续跟进去看:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            /**

            下面的这个postCallback中传入了几个参数,今天我们看的就是这个mTraversalRunnable

            这个参数 final TraversalRunnable mTraversalRunnable = new TraversalRunnable();是一个runnable的接口实现

            我们也知道这个接口只有一个方法 run

            然后我们进入这个方法中,去看看


            */
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

下面这个方法来自Runnable的run方法,唯一的一个方法实现.

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();//这是唯一的入口函数,也是View绘制流程的入口函数

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

viewRootImpl.performTraversals() ==非常重要==

 private void performTraversals() {

     //源码比较长.主要的三个方法是

      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

      performLayout(lp, mWidth, mHeight);

      //源码中做了好多判断,当我们调用invalidate方法的时候,通过各种判断是不会进入前面两个方法的,只会调用perfromDraw方法.
       performDraw();


 }

performTraversals()方法也就是View绘制流程真正的入口,不管是view的重新绘制还是我下面要说的requestLayout()方法都会进入这个方法来处理逻辑.


    private void performDraw() {

        ...

        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

     ...

    }
  private void draw(boolean fullRedrawNeeded) {

        ...

        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {

               ...

            } else {

               ...

                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }

        ...
    }
/**
     * @return true if drawing was successful, false if an error occurred
     */
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

        final Canvas canvas;
        try {
           //锁定我们要绘制那个区域

            canvas = mSurface.lockCanvas(dirty);

            ...

        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            Log.e(mTag, "Could not lock surface", e);

            mLayoutRequested = true;    // ask wm for a new surface next time.
            return false;
        }


        ...


         mView.draw(canvas);

         ...

        return true;
    }

我们可以看到一个mView.draw方法,这个mView不是我们之前想要绘制的那个View而是最顶层的那个View,下面就是View 的绘制流程了,通过mView 这个最顶层的容器,依次往下来onDraw.

总结

  • 当一个view 调用invalidate方法时,先依次调用父容器的invalidateChildInParent方法

  • 最终调用到ViewRootImpl方法中的invalidateChildInParent()方法,然后通过这个方法来实现一个Runnable接口

  • ViewRootImpl的performTraversals() 方法,然后又开始向下分发,给各个子View去绘制自己.

requestLayout()

其实看完invalidate方法,回过头看requestLayout方法就很简单了.

 public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

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

依次往上跑,一直到ViewRootImpl的requestLayout()方法

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        //View调用requestLayout最终层层上传到ViewRootImpl后最终触发了该方法
        scheduleTraversals();
    }
}

代码就不重复贴了,主要的思想是:

requestLayout()方法会调用measure过程和layou过程,不会调用draw过程,所以不会重新绘制任何View(包括该调用者本身)

总结

  • 当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求父view重新调用他的onMeasure、onLayout来重新设置自己位置。

  • 有时我们在改变一个view 的内容之后,可能会造成显示出现错误,比如写ListView的时候 重用convertview中的某个TextView 可能因为前后填入的text长度不同而造成显示出错,此时我们可以在改变内容之后调用requestLayout方法加以解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值