Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Scroll詳解(一):基礎知識

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

編輯:關於Android編程

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

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

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

View的mScrollX和mScrollY

 我們都知道,View中有兩個重要的成員變量,mScrollX,mScrollY.它們分別代表視圖內容(view content)水平方向和豎直方向的滾動距離。我們可以通過setScrollXsetScrollY來個函數來改變它們的值,從而來滾動視圖的內容。
在這裡需要強調的是,mScrollXmScrollY會導致視圖內容(view content)變化,但是不會影響視圖背景(background)。
 看到這裡同學們或許會有寫疑問,視圖的內容和背景有什麼區別呢?視圖還有哪些組成部分呢?
 我們可以從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顯示內容由一下幾個部分組成:

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

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

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

滾動的方向性

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

 

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

 

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繪制過程,請大家多多關注啊。



  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved