Android View相關-View的常用方法及使用區別

經過上一章的摸索,我們已經瞭解了Android中View的繪製流程分別是measure、layout和draw,那麼對Android有一些瞭解的話,一定知道View中有這樣幾個方法invalidate、postInvalidate以及requestLayout,我們知道這些方法調用後會觸發View的重繪(不一定正確的說法),那麼它們的用法是什麼,有什麼區別以及使用時候有哪些注意事項,這就是我們這一篇文章力求弄明白的東西,好了,廢話不多說,開始今天的分析。

invalidate

我們從View源碼中追本溯源,可以看到在View中方法的調用情況是這樣的:

/**
 * 使當前View失效,若View是可見的那麼onDraw方法會在未來的某個時間點被調用
 * 該方法必須在UI線程被調用,若需要在非UI線程調用,調用postInvalidate
 */
public void invalidate() {
    invalidate(true);
}
/**
 * 這個方法是invalidate整整產生作用的地方,一個invalidate請求會將繪製緩存設爲失效
 * 不過這個方式可以設置參數爲false來禁止繪製緩存失效
 */
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 ((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)) {
        // 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);
            //調用父控件的invalidateChild方法
            p.invalidateChild(this, damage);
        }
        ...
    }
}

從這裏可以看出invalidate方法的作用是令當前View的繪製緩存失效,並且會調用onDraw方法,不過我們並沒有在上述代碼中看到onDraw(注意invalidate方法只能在UI線程中調用,工作線程調用該方法會報錯),我們知道該方法最終調用了父控件的invalidateChild方法,那麼我們來查看下ViewGroup中的invalidateChild:

/**
 * 不要主動調用該方法
 */
public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;
    ...
        do {
            ...
            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);
    }
}

該方法執行了一段循環代碼,這裏層層調用父控件的invalidateChildInParent方法,知道返回值爲空,我們知道繪製的起點是在ViewRootImpl中,那麼打開ViewRootImpl,查看其invalidateChildInParent代碼:

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    invalidateRectOnScreen(dirty);
    return null;
}
private void invalidateRectOnScreen(Rect dirty) {
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

這裏在返回null之後上述循環終止,我們注意到在循環終止前ViewRootImpl調用了scheduleTraversals方法,這個方法跟我們之前講過的performTraversals方法很像,我們來看看裏面有什麼:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //這行代碼在Choreographer類內部使用Handler機制最終調用了doTraversal方法
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

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

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        //調用此方法判斷是否需要重新測量放置以及繪製
        performTraversals();

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

可以看到在繞了一圈之後,ViewRootImpl最終會調用我們上篇講到的performTraversals方法,重複判斷流程。至此,View的invalidate方法執行流程完畢。

postInvalidate

我們在前面的註釋裏可以看出,invalidate方法是不可以在非UI線程執行的,而postInvalidate則可以,結合我們之前學習的Handler知識,我們猜想可能是使用了handler機制,最終仍然是調用了invalidate,那我們來源碼中驗證一下我們的猜想:

public void postInvalidate() {
    postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
    // We try only with the AttachInfo because there's no point in invalidating
    // if we are not attached to our window
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
    Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
    mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case MSG_INVALIDATE:
            ((View) msg.obj).invalidate();
            break;
    }
}

可以看到,跟我們猜想的一樣,經過一系列列調用,最後使用Handler機制,調用了invalidate方法,也就是說這兩個方法區別基本只在線程之間。

requestLayout

requestLayout也是我麼View中一個很重要的方法,我們在之前講繪製流程的時候也有看到這個方法,那麼我們來探究一下它究竟做了什麼:

public void requestLayout() {
    ...
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    ...
}

這方法與之前invalidateInternal方法類似,也是一層層調用父控件的requestLayout方法,直到ViewRootImpl(ViewGroup沒有複寫該方法,這裏不再關注):

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

又看到了熟悉的scheduleTraversals方法,這裏跟之前invalidate的區別僅僅只是標誌位可能有所改變,後續流程一致。

小結

一般引起invalidate調用的情景如下:

  • 調用invalidate()方法,請求重新draw(),但只會繪製調用者本身。
  • 調用setSelection()方法 :請求重新draw(),但只會繪製調用者本身。
  • 調用setVisibility()方法 : 當View可視狀態在INVISIBLE轉換VISIBLE時會間接調用invalidate方法,繼而繪製該View。當View的可視狀態在INVISIBLE\VISIBLE 轉換爲GONE狀態時會間接調用requestLayout和invalidate方法,同時由於View樹大小發生了變化,所以會請求measure過程以及draw過程,同樣只繪製需要“重新繪製”的視圖。
  • 調用setEnabled()方法 : 請求重新draw(),但不會重新繪製任何視圖包括該調用者本身。
  • 調用requestFocus方法。請求View樹的draw過程,只繪製“需要重繪”的View。
  • invalidate方法只能在UI線程調用,而postInvalidate可以在工作線程調用,不過invalidate效率更高(顯然)
  • requestLayout()方法會調用measure過程和layout過程,不會調用draw過程,也不會重新繪製任何View包括該調用者本身

好了,這就是本篇的全部內容了,這裏還是偏理論的東西較多,掌握脈絡即可。下一篇文章我們會來聊一聊Android中的事件分發機制,敬請期待~
如果您覺得文章不錯,可以來我的個人博客查看更多內容~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章