1. 程式人生 > >自定義View---invalidate() 方法

自定義View---invalidate() 方法

記錄一下前段時間學習的當呼叫invalidate() 的時候,當前View的onDraw()方法會被呼叫的原因;通過追蹤原始碼可以發現(我這邊看的原始碼版本是25的):
當我們呼叫了View的invalidate()時候,invalidate()往下走呼叫了 invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) 方法,其中在這個方法裡面會呼叫了ViewGroup.invalidateChild(View child, Rect r) 的方法

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        ......
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                // ViewParent是個介面,ViewGroup 中例項化了這個介面 
                // 所以這步呼叫的是 ViewGroup.invalidateChild(View child, Rect r) 的方法
                p.invalidateChild(this, damage);
            }
		...... 
    }

其中這個方法裡面會不斷的呼叫了 parent.invalidateChildInParent(location, dirty);
觸發ViewParent#invalidateChildInParent方法,返回上層父節點ViewParent,若父節點不空,繼續此方法,最終觸發頂層節點ViewRootImpl#invalidateChildInParent方法。

@Override
    public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
           ......

            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }

                if (drawAnimation) {
                    if (view != null) {
                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    } else if (parent instanceof ViewRootImpl) {
                        ((ViewRootImpl) parent).mIsAnimating = true;
                    }
                }

                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                if (view != null) {
                    if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                            view.getSolidColor() == 0) {
                        opaqueFlag = PFLAG_DIRTY;
                    }
                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                    }
                }

                parent = parent.invalidateChildInParent(location, dirty);
                ......
            } while (parent != null);
        }
    }

在ViewRootImpl#invalidateChildInParent(final int[] location, final Rect dirty) 第一行會先檢查我們的當前執行緒是否主執行緒,如果不是則拋異常

@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        ......
        invalidateRectOnScreen(dirty);

        return null;
    }
void checkThread() {
        if (mThread != Thread.currentThread()) {
        // 一般非主執行緒更新UI就報這個
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

沿著ViewRootImpl#invalidateChildInParent(final int[] location, final Rect dirty)方法往下走,最後呼叫了ViewRootImpl#invalidateRectOnScreen(dirty);,這個方法裡面呼叫了ViewRootImpl#scheduleTraversals(); 這個方法

private void invalidateRectOnScreen(Rect dirty) {
        ......
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            scheduleTraversals();
        }
    }
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            ......
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
			......
        }
    }

ViewRootImpl#scheduleTraversals()中mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);這個方法傳了三個引數,其中第二個是一個Runnable物件,這個Runnable物件中只調用了一個 doTraversal(); 方法,在這個方法裡面則呼叫了ViewRootImpl#performTraversals();

void doTraversal() {
        if (mTraversalScheduled) {
            ......
            performTraversals();
			......
        }
    }

ViewRootImpl#performTraversals()這個方法往下走,在2215行裡呼叫了ViewRootImpl#performDraw();的方法
在這裡插入圖片描述
ViewRootImpl#performDraw()裡面往下走呼叫了ViewRootImpl#draw(boolean fullRedrawNeeded)方法

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

在ViewRootImpl#draw(boolean fullRedrawNeeded)裡做的先獲取了mDirty值,該值儲存了需要重繪的區域的資訊,關於檢視重繪,後面會有文章專門敘述,這裡先熟悉一下。接著根據fullRedrawNeeded來判斷是否需要重置dirty區域,最後呼叫了ViewRootImpl#drawSoftware方法,並把相關引數傳遞進去,包括dirty區域

private void draw(boolean fullRedrawNeeded) {
    ...
    //獲取mDirty,該值表示需要重繪的區域
    final Rect dirty = mDirty;
    if (mSurfaceHolder != null) {
        // The app owns the surface, we won't draw.
        dirty.setEmpty();
        if (animating) {
            if (mScroller != null) {
                mScroller.abortAnimation();
            }
            disposeResizeBuffer();
        }
        return;
    }
    //如果fullRedrawNeeded為真,則把dirty區域置為整個螢幕,表示整個檢視都需要繪製
    //第一次繪製流程,需要繪製所有檢視
    if (fullRedrawNeeded) {
        mAttachInfo.mIgnoreDirtyState = true;
        dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    }
    ......
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
        }
}

往下走最終調了ViewRootImpl#drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty)的方法,這個方法裡面調了 mView.draw(canvas); 其中這個mView是我們最外層的View也就是DecorView,再由最外層的View往裡面走呼叫了dispatchDraw(canvas);,一層一層的繪製,最終畫到當前的呼叫了invalidate()方法的View的onDraw

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
    // Draw with software renderer.
    final Canvas canvas;
    try {
        final int left = dirty.left;
        final int top = dirty.top;
        final int right = dirty.right;
        final int bottom = dirty.bottom;
        
        //鎖定canvas區域,由dirty區域決定
        canvas = mSurface.lockCanvas(dirty);

        // The dirty rectangle can be modified by Surface.lockCanvas()
        //noinspection ConstantConditions
        if (left != dirty.left || top != dirty.top || right != dirty.right
                || bottom != dirty.bottom) {
            attachInfo.mIgnoreDirtyState = true;
        }

        canvas.setDensity(mDensity);
    } 

    try {
		......
            //正式開始繪製
            mView.draw(canvas);
		......
        }
    } 
    return true;
}