Android View視圖系統分析和Scroller和OverScroller分析


Android  View視圖系統分析和Scroller和OverScroller分析

View  視圖分析

        首先,我們知道。在Android中全部的視圖資源(無論是Layout還是View),終於的父類都是View類。各式各樣的Layout僅僅是對ViewGroup的一中特別的實現。各種View也僅僅是View的特別實現。

而ViewGroup也是對於View的一種實現。所以說全部的View元素在根本上都是一樣的。當然這並不等於說View == ViewGroup,就好比僅僅有ViewGroup才可以addView。

    當然這里我們的核心並非要講述View與ViewGroup的差別。可是為什么還是要提到全部的View子元素都是一種View呢。接下來我們首先須要分析一下Android 中的View視圖系統。
    
    如果在這里我們每個人都清楚了View的繪制原理,如果不清楚的話能夠看一下這個的博客,我認為解說的非常到位
        當然,假設你不是非常理解也不是非常致命的問題。以下我會做一些解釋。
    (一)非常核心的一點,首先我們要知道不論什么的View要顯示在我們的屏幕上面都要經過 Measure 和 Layout 2個過程
    (二)我們須要充分理解到Canvas的含義

         如果你對於上面我提到的2點有一定程度的理解話。接下來的內容應該就非常好理解了。
    做過JAVA畫圖編程的人都知道Canvas(畫布)的概念,這里,我就不買關子了。我們僅僅須要知道我們全部的視圖元素都會在我們的Canvas上呈現。

    接下來。我先重點分析一下Android中的視圖坐標。
    首先,我們須要知道的事在Android或者是JAVA中Canvas是沒有邊界的,為什么說是沒有邊界的呢,在我之前的一篇博文中自己定義特效VIew第一彈之豎直TextView。我給大家做了一個豎直TextView。在那片博文里面我具體的闡述了Canvas的概念,假設不理解的話能夠轉回去看一看。或者大家能夠這樣理解,反證法。假設我們的Canvas是有邊界的,那么當我們繪制元素時越界了怎么辦,報錯?程序終止執行?所以說,我們的Canvas是沒有邊界的。這樣理解下來我們就明確了Canvas是沒有邊界的,也就是說我們放置我們的View子元素是不會有不論什么的限制的。

同理,這也就解釋了我們隊Canvas採取translate(平移)、clipRect(剪切)一系列變換的意義所在。那么Canvas沒有邊界。是不是就意味着我們的View資源一定都能如我們所願嗎。

    事實上不然,Canvas是沒有邊界的所以我們能夠隨意的放置我們的View子元素(注意這個放置的含義)。可是他們一定都會被我們看到嗎。事實上不然,這就涉及到了我們的Layout坐標了。

Canvas是沒有邊界的,可是我們的Layout區域確實有邊界的。假設,我們希望看到我們的View子元素,那么他就必須在我們的視圖坐標(可視區域),可是在此處還要注意Layout區域並不一定都可見。


    以下我用一張更直觀的圖標來和大家解釋一下吧。



    開啟你手機之中的開發人員選項中的顯示布局邊界就能夠看到這個效果圖了。

我相信在你第一眼看到這張圖片的時候你就能夠瞬間的對於Layout坐標(視圖區域)有了一個認識了。沒錯,你所示那些藍角紅線組成的長方形區域就是我們的布局區域,那么他們的框定是源自於哪里呢。

這里還記得我上面強調過的

一)非常核心的一點,首先我們要知道不論什么的View要顯示在我們的屏幕上面都要經過 Measure 和 Layout 2個過程
    是的,沒錯你猜對了,他們源自於Layout(Layout框定了他們的顯示位置和大小)。

    這里就解釋了我們為什么網上能夠通過
LastCallMessageContainer.layout(left, top, left+ LastCallMessageContainer.getWidth(), top+ LastCallMessageContainer.getHeight());

這樣的方式來實現尾隨手指移動的View。可是這樣的方式好不好呢。答案非常明顯,並不好,有機會的話給打擊分享一下實現方式吧。
     在這里。我還要繼續啰嗦一下,大家必需要有的概念,是不是我們的Layout區域都一定可見呢。答案是否定的。所以說這里我們還會有第三個區域(可視區域可視坐標)。那么什么是可視區域,可視坐標呢。事實上說白了就是你的手機屏幕坐標,所以說我們希望見到我們的視圖元素,必須滿足:
     一,盡管Canvas是沒有邊界的,可是Layout區域是有邊界的,所以我們的視圖元素須要在我們的Layout區域中
     二,Layout區域必須在可視區域內(手機屏幕)。
     

scrollTo()和scrollBy()分析

    好的,我們繼續回歸正題,假定在這里每個人對於Canvas和Layout都有了足夠的認識。

    那么問題來了,我們能夠想象一下:
    一、我們部分子元素的Layout區域在可視區域之外。我們想看見他怎么辦?
    二、盡管我們可以看到子元素。可是有一些我們不想看到他或者想看見其它看不見的又怎么辦呢?

    是不是認為非常繞,可是沒辦法就是這么繞,對於第一個問題,大家能夠想象一下。假設讓你來設計ViewPager,你認為你會怎么設計。對於第二個問題一般就會用的非常多的,比方說拖拽View效果。

    那么,如今非常明顯了。我們希望做到以上兩點,誰能夠幫助我們 ,何以解憂 ,唯有ScrollTo和ScrollBy

    那么要說ScrollTo和ScrollBy,我們必須先說的事mScrollX和mScrollY

    在View.java中提供了了例如以下兩個變量以及對應的屬性方法去讀取滾動值 ,例如以下: View.java類中   

[java]  view plain copy print ?
  1. /** 
  2.      * The offset, in pixels, by which the content of this view is scrolled 
  3.      * horizontally. 
  4.      * {@hide} 
  5.      */  
  6.     protected int mScrollX;   //該視圖內容相當於視圖起始坐標Layout的偏移量   , X軸 方向  
  7.     /** 
  8.      * The offset, in pixels, by which the content of this view is scrolled 
  9.      * vertically. 
  10.      * {@hide} 
  11.      */  
  12.     protected int mScrollY;   //該視圖內容相當於視圖起始坐標Layout的偏移量   , Y軸方向  
  13.   
  14.     /** 
  15.      * Return the scrolled left position of this view. This is the left edge of 
  16.      * the displayed part of your view. You do not need to draw any pixels 
  17.      * farther left, since those are outside of the frame of your view on 
  18.      * screen. 
  19.      * 
  20.      * @return The left edge of the displayed part of your view, in pixels. 
  21.      */  
  22.     public final int getScrollX() {  
  23.         return mScrollX;  
  24.     }  
  25.   
  26.     /** 
  27.      * Return the scrolled top position of this view. This is the top edge of 
  28.      * the displayed part of your view. You do not need to draw any pixels above 
  29.      * it, since those are outside of the frame of your view on screen. 
  30.      * 
  31.      * @return The top edge of the displayed part of your view, in pixels. 
  32.      */  
  33.     public final int getScrollY() {  
  34.         return mScrollY;  
  35.     }  

     那么,究竟什么叫做視圖起始坐標Layout的偏移量呢。那么我們返回去。就最上面那個圖片你認為我們的TextView的mScrollX 和mScrollY 為多少呢。答案是0。看一下你能不能有所理解。簡而言之就是說TextView的text(文本內容)在Layout布局中的坐標為(0,0)。TextView的text(內容文本)偏移量也為0.

     通過上下這兩張圖片,我相信大家對與mScrollX 和mScrollY有一個初步的認識了吧,代碼我就不貼了。太簡單了。Hello Word后面的圖案是TextView的背景。

    這時,我相信大家對與視圖起始坐標Layout的偏移量應該是有一定的認識了,這里順帶要提一下的內容偏移量對背景無效。比方我們上圖TextView的背景就沒有一起移動。
     

public void scrollTo(int x, int y)

              說明:在當前視圖內容偏移至(x , y)坐標處,即位於Layout區域(x , y)坐標處。

        方法原型為: View.java類中

[java]  view plain copy print ?
  1. /** 
  2.  * Set the scrolled position of your view. This will cause a call to 
  3.  * {@link #onScrollChanged(int, int, int, int)} and the view will be 
  4.  * invalidated. 
  5.  * @param x the x position to scroll to 
  6.  * @param y the y position to scroll to 
  7.  */  
  8. public void scrollTo(int x, int y) {  
  9.     //偏移位置發生了改變  
  10.     if (mScrollX != x || mScrollY != y) {  
  11.         int oldX = mScrollX;  
  12.         int oldY = mScrollY;  
  13.         mScrollX = x;  //賦新值,保存當前廉價量  
  14.         mScrollY = y;  
  15.         //回調onScrollChanged方法  
  16.         onScrollChanged(mScrollX, mScrollY, oldX, oldY);  
  17.         if (!awakenScrollBars()) {  
  18.             invalidate();  //一般都引起重繪  
  19.         }  
  20.     }  
  21. }  

 

     public void scrollBy(int x, int y)    

            說明:在當前視圖內容在Layout布局中繼續偏移(x , y)個單位。

        方法原型為: View.java類中

[java]  view plain copy print ?
  1. /** 
  2.    * Move the scrolled position of your view. This will cause a call to 
  3.    * {@link #onScrollChanged(int, int, int, int)} and the view will be 
  4.    * invalidated. 
  5.    * @param x the amount of pixels to scroll by horizontally 
  6.    * @param y the amount of pixels to scroll by vertically 
  7.    */  
  8.   // 看出原因了吧 。

     mScrollX 與 mScrollY 代表我們當前偏移的位置 , 在當前位置繼續偏移(x ,y)個單位  

  9.   public void scrollBy(int x, int y) {  
  10.       scrollTo(mScrollX + x, mScrollY + y);  
  11.   }  
      上面僅僅是用TextView來讓大家充分理解內容的偏移。一般來講這樣做事實上並沒有什么意義。那么ScrollTo和ScrollBy的強大之處在哪呢。

      舉個樣例來說,大家能夠想象一下一個寬和高都是match_parent的ViewPager,假設說他當中有3頁,那么我們非常明顯受手機屏幕的限制,我們僅僅能看到第1頁,那么假設我們對自己的ViewPager調用scrollBy。讓他延X軸反向滾動屏幕的寬度,那么請問我們能夠看到第幾頁。這個問題和代碼都非常easy,這里我就不細說了。

大家自己想一想。



      無論了,說了這么多,我僅僅能默認大家對與mScrollX 和mScrollY和ScrollTo和ScrollBy有了足夠的認識了。


Scroller 和OverScroller分析

     我們知道想把一個View的內容偏移至指定坐標(x,y)處,利用scrollTo()方法直接調用就OK了。但我們不能忽視的是,該方法本身來的的副作用:很迅速的將View/ViewGroup偏移至目標點,而沒有對這個偏移過程有不論什么控制。對用戶而言可能是不太友好的。於是,基於這樣的偏移控制,Scroller類被設計出來了,該類的主要作用是為偏移過程制定一定的控制流程(后面我們會知道的很多其它),從而使偏移更流暢,更完美。


     可能上面說的比較懸乎,道理也沒有講透。以下我就依據特定情景幫助大家分析下:


        情景: 從上海怎樣到武漢?

            普通的人可能會想,so easy : 飛機、輪船、11路公交車...

            文藝的人可能會想,  小 case : 時空忍術(火影的招數)、翻個筋斗(孫大聖的招數)...


     無論怎么樣,我們想出來的套路可能有兩種:

               1、有個時間控制過程才干抵達(緩慢的前進)                              -----     相應於Scroller的作用

                      如果做火車,這個過程可能包含: 火車速率,花費周期等。

               2、瞬間移動(超神太快了。都眩暈了,用戶體驗不太好)                     ------   相應於scrollTo()的作用


     在這里。我必須第一時間強調一下:Scrollers並非控制View進行滾動,包含內容或者是位置,實際上。Scrollers僅僅是一個控件移動軌跡的輔助計算類,假設你想滾,他能幫你計算什么時間應該滾到什么位置,可是滾不滾,全靠你自覺~所以說。滾動位置由Scrollers計算出來了,我們在什么時候滾呢?滾多少呢?這時候,就要View的一個回調函數computeScroll()出馬了。

    我們看看View里面的computeScroll()做了些什么


/**
     * Called by a parent to request that a child update its values for mScrollX
     * and mScrollY if necessary. This will typically be done if the child is
     * animating a scroll using a {@link android.widget.Scroller Scroller}
     * object.
     */
    public void computeScroll() {
    }

      空的。意思非常明顯。自己加入。

computeScroll()方法介紹

         

     為了易於控制滑屏控制,Android框架提供了 computeScroll()方法去控制這個流程。在繪制View時。會在draw()過程調用該方法。因此。 再配合使用Scroller實例。我們就能夠獲得當前應該的偏移坐標,手動使View/ViewGroup偏移至該處。

     computeScroll()方法原型例如以下,該方法位於ViewGroup.java類中      

[java]  view plain copy print ?

  1. /** 
  2.      * Called by a parent to request that a child update its values for mScrollX 
  3.      * and mScrollY if necessary. This will typically be done if the child is 
  4.      * animating a scroll using a {@link android.widget.Scroller Scroller} 
  5.      * object. 
  6.      */由父視圖調用用來請求子視圖依據偏移值 mScrollX,mScrollY又一次繪制  
  7.     public void computeScroll() { //空方法 。自己定義ViewGroup必須實現方法體  
  8.           
  9.     }  

          為了實現偏移控制,一般自己定義View/ViewGroup都須要重載該方法 。

 

     其調用過程位於View繪制流程draw()過程中,例如以下:

[java]  view plain copy print ?
  1. @Override  
  2. protected void dispatchDraw(Canvas canvas){  
  3.     ...  
  4.       
  5.     for (int i = 0; i < count; i++) {  
  6.         final View child = children[getChildDrawingOrder(count, i)];  
  7.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  8.             more |= drawChild(canvas, child, drawingTime);  
  9.         }  
  10.     }  
  11. }  
  12. protected boolean drawChild(Canvas canvas, View child, long drawingTime) {  
  13.     ...  
  14.     child.computeScroll();  
  15.     ...  
  16. }  
     既然如今我們知道了:
      一、Scroller僅僅是一個滾動計算輔助類。並不能實現滾動要求。

      二、我們的滾動要在computeScroll中自己去寫。


     那么。我們應該怎么開啟滾動呢。以下我們就來系統的介紹一下我們的Scroller 和 OverScroller。

      

         其實講到這里,有的同學可能比較迷惑,OverScroller和Scroller有什么差別呢?其實,這兩個類都屬於Scrollers,Scroller出現的比較早,在API1就有了。OverScroller是在API9才加入上的,出現的比較晚,所以功能比較完好,Over的意思就是超出。即OverScroller提供了對超出滑動邊界的情況的處理,這兩個類80%的API是一致的。OverScroller比Scroller加入了一下幾個方法


    ☞ isOverScrolled()
    ☞ springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) 
    ☞ fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY)
    ☞ notifyHorizontalEdgeReached(int startX, int finalX, int overX)
    ☞ notifyVerticalEdgeReached(int startY, int finalY, int overY)


    從名字也能看出來。都是對Over功能的支持。其它的API都一樣。所以介紹通用API的時候。並不區分OverScroller和Scroller。


    以下簡介一下經常使用的API。

 ☞ startScroll(int startX, int startY, int dx, int dy) 

 ☞ startScroll(int startX, int startY, int dx, int dy,int duration) 

      核心用來開啟滾動的方法:

    

參數

      startX 滾動起始值

startY 滾動起始值

dx 水平方向滑動的距離,正值會使滾動向左滾動

dy 垂直方向滑動的距離。正值會使滾動向上滾動

     duration    滾動持續時間,以毫秒計。缺省值250ms作為持續時間。

    ☞ computeScrollOffset() 這個函數非常核心,表面上來看他僅僅是用來推斷我們的滾動是否結束

if (mScroller.computeScrollOffset())
       看起來似乎沒有什么價值,起始不然

//依據當前已經消逝的時間計算當前的坐標點,保存在mCurrX和mCurrY值中  
public boolean computeScrollOffset() {  
if (mFinished) {  //已經完畢了本次動畫控制,直接返回為false 
return false;  
}
.......
mScrollerX.updateScroll(q);
mScrollerY.updateScroll(q);
.......
}

這就是為什么我們稱Scroller為滾動的助手類,原來如此,我們在startScroll中設好初始值 、滾動距離、滾動時間。那么
computeScrollOffset會幫我們計算出在特定的時間內應該滾動在什么地方,到時候我們僅僅要通過getCurrX()和getCurrY()得到就能夠了

☞ getCurrX() 這個就是獲取當前滑動的坐標值,由於Scrollers僅僅是一個輔助計算類,所以假設我們想獲取滑動時的時時坐標,就能夠通過這種方法獲得。然后在computeScroll()里面調用

    ☞ getFinalX() 這個是用來獲取終於滑動停止時的坐標

    ☞ isFinished() 用來推斷當前滾動是否結束


那么 。到了這里我們就一目了然。Scroller的基本用法不外乎:

mScroller.startScroll(<span style="color: rgb(51, 51, 51); font-family: 宋體;font-size:18px; line-height: 30px; text-indent: 28px;">startX ,<span style="color: rgb(51, 51, 51); font-family: 宋體;font-size:18px; line-height: 30px; text-indent: 28px;">startY,dx,dy,duration</span></span>);
@Override
    public void computeScroll() {

        // 先推斷mScroller滾動是否完畢
        if (mScroller.computeScrollOffset()) {
            // 這里調用View的scrollTo()完畢實際的滾動
            scrollTo( mScroller.getCurrX(), mScroller .getCurrY());
            // 必須調用該方法,否則不一定能看到滾動效果
            invalidate();
        }
        super.computeScroll();
    }

那么到了這里,基本上我們對於Scroller的基本使用應該就不是問題了。

  今天就到這里吧。下次再寫個高於高級使用續篇吧。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM