Android Scroll詳解(一):基礎(chǔ)知識

原作者: ztelur

本文僅供個人學(xué)習(xí),不用于任何形式商業(yè)目的,轉(zhuǎn)載請注明原作者、文章來源、翻譯作者及簡書鏈接,版權(quán)歸原文作者所有。

在前邊的文章中,我們已經(jīng)對Android觸摸事件處理有了大致的了解,并且詳細探討了MotionEvent的相關(guān)用法。對之前文章中的知識還不是很了解的同學(xué),請閱讀《Android MotionEvent詳解》
?今天,我們就來探討一下Android中界面滾動效果的相關(guān)機制,本篇文章主要講解一下滾動相關(guān)的知識點,之后的文章會涉及實際的代碼和原理。希望大家閱讀完這篇文章之后,能夠了解或者掌握一下知識:

  • Android 視圖的組成部分
  • mScrollXmScrollY對視圖顯示的影響
  • scrollToscrollBy的使用
  • invalidatepostInvalidate的區(qū)別

View的mScrollX和mScrollY

我們都知道,View中有兩個重要的成員變量,mScrollX,mScrollY.它們分別代表視圖內(nèi)容(view content)水平方向和豎直方向的滾動距離。我們可以通過setScrollXsetScrollY來個函數(shù)來改變它們的值,從而來滾動視圖的內(nèi)容。
?在這里需要強調(diào)的是,mScrollXmScrollY會導(dǎo)致視圖內(nèi)容(view content)變化,但是不會影響視圖背景(background)。
?看到這里同學(xué)們或許會有寫疑問,視圖的內(nèi)容和背景有什么區(qū)別呢?視圖還有哪些組成部分呢?
?我們可以從View的draw方法中得知View的組成部分。

// http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/view/View.java#View
public void draw(Canvas canvas) {
         ........
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
        .......

        // Step 2, save the canvas' layers
        .......
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        .......
        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }
        .....
        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);
        ......
    }

View顯示內(nèi)容由一下幾個部分組成:

  • 背景(background)
  • 本身的內(nèi)容(content)
  • 子視圖
  • 邊界漸變效果(fade effect),上下左右四個邊界都可能會有漸變效果,代碼中只顯示了上邊界的漸變效果繪制。
  • 邊框或者裝飾效果(decorations),比如滾動條

舉個例子吧,我們都知道在布局文件中,TextView有兩個比較重要的屬性:background,text。background可以設(shè)置TextView的背景,而text則是設(shè)置要繪制字體內(nèi)容。

    <TextView
        android:layout_width="wrap_content"
        android:background="@drawable/ic_launcher"
        android:text="Test"
        android:layout_height="wrap_content" />

mScrollXmScrollY對除了本身內(nèi)容外的部分的繪制都有影響。只是不會影響視圖背景的繪制。

滾動的方向性

我們都知道,在Android的視圖中,布局相關(guān)的數(shù)值都是有方向性的,比如mLeft,mTop。

View坐標(biāo)軸.png

由上圖我們可以知道,Android視圖坐標(biāo)的原點在屏幕的左上方,x軸正方向是向右,y軸正方向是向下。
?所以,當(dāng)你將mLeftmTop的數(shù)值加10并且重繪視圖時,視圖會向右下移動。
?那么mScrollYmScrollX也在這樣一個坐標(biāo)域中嗎?它們的正方向和mTopmLeft是一樣的嗎?是的,它們屬于同一個坐標(biāo)域,方向性相同。
?但是如果你將mScrollXmScrollY的數(shù)值都增大10,然后調(diào)用invalidate()重新繪制界面的話,你會發(fā)現(xiàn)視圖中的內(nèi)容都向左上角移動啦!
?這是怎么回事呢?從概念上你可以先這樣解:mScrollXmScrollY改變導(dǎo)致View的可視區(qū)域的移動,并不是導(dǎo)致View的視圖區(qū)域的移動。
?View的視圖區(qū)域相當(dāng)于無限大的,你可以在onDraw函數(shù)中的canvas中繪制任意大的圖像,但是你會發(fā)現(xiàn),最終屏幕上顯示出來的只會是一部分,因為View自身還有大小概念,也就是measurelayout時,視圖會被設(shè)置長寬還有界面中位置,這樣的話,視圖可視區(qū)域就被確定啦。
?做一個形象的比喻。View的可視區(qū)域就是一面墻上的窗戶,View的視圖區(qū)域就相當(dāng)于墻后邊的優(yōu)美景色。墻外風(fēng)光無線,但是你只能看到窗戶中的景色。如果窗戶變大啦,外邊風(fēng)景不變,你看到的景色就大了一點;如果窗戶向右下角移動了一段距離,你就會發(fā)現(xiàn)外邊的景色好像是向左上角"移動"了一段距離。

view scroll example.png

ScrollTo 和 ScrollBy

這兩個函數(shù)是用來滾動視圖的API

    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

大家看源代碼很容易就理解了二者的作用和區(qū)別:scrollTo就是直接改變mScrollXmScrollY;而scrollBy則是給mScrollXmScrollY加上增量。

invalidate和postInvalidate

上邊這兩個函數(shù)都是請求視圖重新繪制的API,但是二者的使用有些區(qū)別。
?invalidate必須在主線程(UI Thread)中調(diào)用,而postInvalidate可以在非主線程(Non UI Thread)中調(diào)用。
?除此之外,二者還有點小區(qū)別。
?調(diào)用invalidate時,它會檢查上一次請求的UI重繪是否完成,如果沒有完成的話,那么它就什么都不做。

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
            .....
         //DRAWN和HAS_BOUNDS是否被設(shè)置為1,說明上一次請求執(zhí)行的UI繪制已經(jīng)完成,那么可以再次請求執(zhí)行
        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 AttachInfo ai = mAttachInfo;
                final ViewParent p = mParent;
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);//TODO:這是invalidate執(zhí)行的主體
                .....
        }
    }

postInvalidate則不會這樣,它是向主線程發(fā)送個Message,然后handleMessage時,調(diào)用了invalidate()函數(shù)。

//View.java
    public void postInvalidateDelayed(long delayMilliseconds) {
    ...               attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    ...
    }

// ViewRootImpl 發(fā)送Message
    public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }

// ViewRootImpl 處理Message
public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_INVALIDATE:
                ((View) msg.obj).invalidate();
                break;
            }
}   

所以,二者的調(diào)用時機還是有區(qū)別的,就比如使用Scroller進行視圖滾動時,二者的調(diào)用就有所不同。

后續(xù)

之后還有會兩篇博文,一篇是《Android Scroll詳解(二):OverScroller實戰(zhàn)》講解具體代碼實現(xiàn),另外一篇是《Android Scroll詳解(三):Android 繪制過程詳解》主要是從滾動角度理解Android繪制過程,請大家多多關(guān)注啊。

參考文章

http://stackoverflow.com/questions/7596370/what-is-the-difference-between-androids-invalidate-and-postinvalidate-metho

http://www.programering.com/a/MDN3QDNwATQ.html

http://blog.csdn.net/xiaanming/article/details/17483273

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容