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

在前邊的文章中,咱們已經對Android觸摸事件處理有了大體的瞭解,而且詳細探討了MotionEvent的相關用法。對以前文章中的知識還不是很瞭解的同窗,請閱讀《Android MotionEvent詳解》html

今天,咱們就來探討一下Android中界面滾動效果的相關機制,本篇文章主要講解一下滾動相關的知識點,以後的文章會涉及實際的代碼和原理。但願你們閱讀完這篇文章以後,可以瞭解或者掌握一下知識:java

  • Android 視圖的組成部分android

  • mScrollXmScrollY對視圖顯示的影響git

  • scrollToscrollBy的使用github

  • invalidatepostInvalidate的區別canvas

View的mScrollX和mScrollY

咱們都知道,View中有兩個重要的成員變量,mScrollX,mScrollY.它們分別表明視圖內容(view content)水平方向和豎直方向的滾動距離。咱們能夠經過setScrollXsetScrollY來個函數來改變它們的值,從而來滾動視圖的內容。app

在這裏須要強調的是,mScrollXmScrollY會致使視圖內容(view content)變化,可是不會影響視圖背景(background)。函數

看到這裏同窗們或許會有寫疑問,視圖的內容和背景有什麼區別呢?視圖還有哪些組成部分呢?佈局

咱們能夠從View的draw方法中得知View的組成部分。post

// 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顯示內容由一下幾個部分組成:

  • 背景(background)

  • 自己的內容(content)

  • 子視圖

  • 邊界漸變效果(fade effect),上下左右四個邊界均可能會有漸變效果,代碼中只顯示了上邊界的漸變效果繪製。

  • 邊框或者裝飾效果(decorations),好比滾動條

舉個例子吧,咱們都知道在佈局文件中,TextView有兩個比較重要的屬性:background,textbackground能夠設置TextView的背景,而text則是設置要繪製字體內容。

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

mScrollXmScrollY對除了自己內容外的部分的繪製都有影響。只是不會影響視圖背景的繪製。

滾動的方向性

咱們都知道,在Android的視圖中,佈局相關的數值都是有方向性的,好比mLeft,mTop

View座標軸

由上圖咱們能夠知道,Android視圖座標的原點在屏幕的左上方,x軸正方向是向右,y軸正方向是向下。因此,當你將mLeftmTop的數值加10而且重繪視圖時,視圖會向右下移動。

那麼mScrollYmScrollX也在這樣一個座標域中嗎?它們的正方向和mTopmLeft是同樣的嗎?是的,它們屬於同一個座標域,方向性相同。

可是若是你將mScrollXmScrollY的數值都增大10,而後調用invalidate()從新繪製界面的話,你會發現視圖中的內容都向左上角移動啦!

這是怎麼回事呢?從概念上你能夠先這樣解:mScrollXmScrollY改變致使View的可視區域的移動,並非致使View的視圖區域的移動。

View的視圖區域至關於無限大的,你能夠在onDraw函數中的canvas中繪製任意大的圖像,可是你會發現,最終屏幕上顯示出來的只會是一部分,由於View自身還有大小概念,也就是measurelayout時,視圖會被設置長寬還有界面中位置,這樣的話,視圖可視區域就被肯定啦。

作一個形象的比喻。View的可視區域就是一面牆上的窗戶,View的視圖區域就至關於牆後邊的優美景色。牆外風光無線,可是你只能看到窗戶中的景色。若是窗戶變大啦,外邊風景不變,你看到的景色就大了一點;若是窗戶向右下角移動了一段距離,你就會發現外邊的景色好像是向左上角"移動"了一段距離。

View scroll例子

ScrollTo 和 ScrollBy


這兩個函數是用來滾動視圖的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);
    }


你們看源代碼很容易就理解了兩者的做用和區別:scrollTo就是直接改變mScrollXmScrollY;而scrollBy則是給mScrollXmScrollY加上增量。

invalidate和postInvalidate


上邊這兩個函數都是請求視圖從新繪製的API,可是兩者的使用有些區別。

invalidate必須在主線程(UI Thread)中調用,而postInvalidate能夠在非主線程(Non UI Thread)中調用。

除此以外,兩者還有點小區別。

調用invalidate時,它會檢查上一次請求的UI重繪是否完成,若是沒有完成的話,那麼它就什麼都不作。

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
            .....
         //DRAWN和HAS_BOUNDS是否被設置爲1,說明上一次請求執行的UI繪製已經完成,那麼能夠再次請求執行
        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執行的主體
                .....
        }
    }


postInvalidate則不會這樣,它是向主線程發送個Message,而後handleMessage時,調用了invalidate()函數。

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

// ViewRootImpl 發送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;
            }
}


因此,兩者的調用時機仍是有區別的,就好比使用Scroller進行視圖滾動時,兩者的調用就有所不一樣。

後續

以後還有會兩篇博文,一篇是《Android Scroll詳解(二):OverScroller實戰》講解具體代碼實現,另一篇是《Android Scroll詳解(三):Android 繪製過程詳解》主要是從滾動角度理解Android繪製過程,請你們多多關注啊。

參考文章

相關文章
相關標籤/搜索