从源码解析computescroll()方法与invalidate()的关系

本篇文章是有关于自定义控件的,为什么我会写这篇文章呢,也是因为自己在写一个自定义轮播图的控件的时候想到一些东西,促使我想要写这篇文章。在写完轮播图之后整理下思路,将这个过程跑一遍,感觉东西还是挺多的。我想将这个理解了,对以后自己自定义view会熟悉更多吧。
这篇文章我会着重讲解computeScroll()方法跟invalidate()之间的关系。乍一看,啥关系都没有,其实底层上看,关系也并不是很大,只是在我们硬件触发绘制操作的时候有点关系。这个放到后面说。我们先看computeScroll()方法。它是被view的draw()方法调用。我们看看draw()方法做了什么,

            /**
             * This method is called by ViewGroup.drawChild() to have each child view draw itself.
             *
             * This is where the View specializes rendering behavior based on layer type,
             * and hardware acceleration.
            */  
            boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
            final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();

            boolean drawingWithRenderNode = mAttachInfo != null
                    && mAttachInfo.mHardwareAccelerated
                    && hardwareAcceleratedCanvas;

            ......

            int sx = 0;
            int sy = 0;
            if (!drawingWithRenderNode) {
                computeScroll();
                sx = mScrollX;
                sy = mScrollY;
            }

            ......

            return more;
        }

这个方法的代码比较多,我们只看重点部分,我们看到要想执行computeScroll()方法,必须是drawingWithRenderNode的值是false,而drawingWithRenderNode的值由上面三个条件一起控制,这个mAttachInfo 是一个封装了view的很多属性的一个类,所以他不为空,然后是view的mHardwareAccelerated是代表他是否在绘制的时候提供硬件加速,我们一般默认是不开启的,即使开启也没事,我们不是还有第三个条件来控制么,我们看看第三个条件是怎么来的,我们看到方法的第一行就有,他是代表canvas是否是硬件加速,我们再进去看看,

public boolean isHardwareAccelerated() {
        return false;
    }

我们看到默认是false的,也就是说我们的画布是不开启硬件加速的,所以hardwareAcceleratedCanvas;这个值是为false的,那么不管怎么样都会去执行computeScroll()方法了。OK在draw()方法中我们执行了computeScroll()方法,那么那里会执行draw()方法呢,我们继续去探索,我们看到draw()方法的注释,我想这个不用过多解释吧,说明是在viewgroup的drawChild()方法被执行,所以我们去瞧瞧,

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            return child.draw(canvas, this, drawingTime);
        }

果然是这里,但是这个方法的代码也太。。。。少了吧,不过直接明了,哈哈,所以我们得继续查看drawChild()方法是在哪里被调用了,发现是被viewgroup的dispatchDraw(Canvas canvas)方法调用了。所以我们继续查看dispatchDraw()是被谁调用的呢?发现是被view的draw()方法调用的,但是注意,此draw()方法并非彼draw()方法,这个draw()方法是被系统硬件调度的(也可以说是系统通过schedule线程去调度的),所以它的源头就到此结束了(因为再往里层走就是系统层的代码了)。 OK,到此computeScroll()方法的全程调用已经分析完毕。
我们继续看invalidate()方法的调用,

public void invalidate() {
        invalidate(true);
    }

我们继续寻找:

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

            // Damage the entire IsolatedZVolume receiving this view's shadow.
            if (isHardwareAccelerated() && getZ() != 0) {
                damageShadowReceiver();
            }
        }
    }

终于不是一行代码了,我们找到重要的地方,p.invalidateChild(this, damage),在这里是对子view进行相应的绘制,我们看到是p来调用它,那我们来看看这个p是什么呢?我们去寻找这个p的来源,

/*
     * Caller is responsible for calling requestLayout if necessary.
     * (This allows addViewInLayout to not request a new layout.)
     */
    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");
        }
    }

发现这个p的值是在这里被赋值的,那么这个方法何时被调用呢,我们先在ViewGroup中去看,

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

       ...

        // tell our children
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }

      ...
    }   

发现还真的在这里调用了,但是别急,我们看看是不是真的执行了这个方法,我们去寻找 addViewInner()的调用者,

public void addView(View child, int index, LayoutParams params) {
        if (DBG) {
            System.out.println(this + " addView");
        }

        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }

        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }      

在最后一行,我们看到了调用它,但是最后一个参数赋值是false,什么鬼,也就是说 child.assignParent(this)并没有被执行,
所以说 assignParent()不是在ViewGroup中被执行的,别急,这个方法不止这一个地方被执行,他还会在ViewRootImpl中执行,不信你看;

 /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
           ......
                view.assignParent(this);
           .....
    } 

看到这里我们心放下一半了(好怕没地方执行啊,哈哈),那么这个setView又是在哪被执行的,实际上是在Activity的启动过
程执行的,参考http://blog.csdn.net/u013866845/article/details/54408825这篇文章,我们发现,最后发现这个mParent的值是
ViewRootImpl。OK,漫长的寻“父”之路终于结束了,让我们回到这里:

if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }

也就是说这个p是ViewRootImpl,我们就看看ViewRootImpl的invalidateChild()方法了,

 @Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }

又来了,我们继续找吧,

@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

        if (dirty == null) {
            return null;
            invalidate();
        } 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;
    }   

发现最后不管怎样都调用到了invalidateRectOnScreen(dirty)方法了,继续往下:

private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;
        if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
            mAttachInfo.mSetIgnoreDirtyState = true;
            mAttachInfo.mIgnoreDirtyState = true;
        }

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

因为我们的View正在进行重绘的动作,所以这个if条件是成立的(这里就不继续寻找了),所以我们进入到scheduleTraversals方法中去,

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }   

有木有很熟悉的赶脚,原来在这里通过线程来定时执行重绘任务了,说到我们不得不提一下在startScroll()的时候的代码
了,其实在startScroll()的时候是有一个默认的时间的,也就是说在这个默认的时间里去执行者一系列的重绘动作,所以这个线
程在这个时间段里会不停的去触发重绘的动作,也就是draw()动作,也就进行了我们Vieww的重绘了。到这里,我们也就清楚了整个流程了,我们终于是揭开了computescroll()方法与invalidate()的整个过程的面纱了,相信看过之后对这一系列有了更加清楚的了解了吧。
希望本篇文章会对您有一定的帮助,有不足的地方还望大家指正,谢谢!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值