Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android編程入門 >> 詳解實現Android中實現View滑動的幾種方式

詳解實現Android中實現View滑動的幾種方式

編輯:Android編程入門

注: 本文提到的所有三種滑動方式的完整demo:ScrollDemo

1. 關於View我們需要知道的

(1)什麼是View?

    Android中的View類是所有UI控件的基類(Base class),也就是說我們平時所有到的各種UI控件,比如Button、ImagView等等都繼承自View類。LinearLayout、FrameLayout等布局管理器的直接父類是ViewGroup,而ViewGroup也有View類派生。總的來說,View是對UI控件的抽象,它代表了屏幕上的一個矩形區域。通過繼承View,並重寫相應方法,我們就能夠實現具有各種外觀及行為的UI控件。Button等控件我們之所以能夠直接拿來即用,是因為Google已經幫我們完成了繼承View並重寫方法的工作。

(2)View的位置

    View在屏幕上的位置由它的以下四個參數所決定:

  • top:View的左上角的縱坐標,對應著View類中的成員變量mTop,可由getTop方法獲得;
  • left:View的左上角的橫坐標,對應著View類中的成員變量mLeft,可由getLeft方法獲得;
  • bottom:View的右下角的縱坐標,對應著View類中的成員變量mBottom,可由getBottom方法獲得;
  • right:View的右下角的橫坐標,對應著View類中的成員變量mRight,可由getRight方法獲得。

    注意,以上的坐標都是相對於父View來說的,也就是說,坐標都是相對坐標,因為子View的布局是由父View來完成的。如下圖所示:

 

     有了這四個參數,計算View的寬高就很容易了:width = right - left;height = bottom - top。關於View還有兩個參數需要我們注意:translationX代表View平移的水平距離,translationY代表View平移的豎直距離;x、y分別為View的左上角的橫縱坐標。View若經過了平移,改變的是它的x、y(代表當前View的左上角位置),它的四個位置參數代表了View的原始位置信息,是始終不變的。View在平移的過程中始終滿足如下關系:

    x = left + translationX; y = top + translationY。

 

2. 實現View滑動的幾種方式

    我們在使用View的過程中,經常需要實現View的滑動效果。比如ListView、跟隨手指而移動的自定義View等等,前者的滑動效果是SDK為我們提供的,而對於我們自定義View的滑動效果就需要我們自己來實現。下面我們來詳細介紹以下實現View滑動的幾種方式。

(1)使用scrollTo/scrollBy實現View的滑動

    實現滑動的最樸素直接的方式就是使用View類自帶的scrollTo/scrollBy方法了。scrollBy方法是滑動指定的位移量,而scrollTo方法是滑動到指定位置。這兩個方法的源碼如下:

 /** 
   * Set the scrolled position of your view. This will cause a call to 

   * {@link #onScrollChanged(int, int, int, int)} and the view will be 

   * invalidated. 

   * @param x the x position to scroll to 

   * @param y the y position to scroll to 

   */  
public void scrollTo(int x, int y) {  
14       if (mScrollX != x || mScrollY != y) {  
15           int oldX = mScrollX;  
16           int oldY = mScrollY;  
17           mScrollX = x;  
18           mScrollY = y;  
19           onScrollChanged(mScrollX, mScrollY, oldX, oldY);  
20           if (!awakenScrollBars()) {  
21               invalidate();  
22           }  
23       }  
24 }  
25 
/** 
    * Move the scrolled position of your view. This will cause a call to 
    * {@link #onScrollChanged(int, int, int, int)} and the view will be 
    * invalidated. 
    * @param x the amount of pixels to scroll by horizontally 
    * @param y the amount of pixels to scroll by vertically 
*/  
public void scrollBy(int x, int y) {  
34       scrollTo(mScrollX + x, mScrollY + y);  
35 }  

 

    通過以上代碼的33~35行我們可以看到,scrollBy方法內部也是調用了scrollTo方法來實現。以上源碼中我們注意到了mScrollX和mScrollY成員變量,前者是View的左邊緣減去View的內容的左邊緣,後者是View的上邊緣減去View的內容的上邊緣。示意圖如下:

    上圖中,黑色邊框代表View在屏幕上對應的矩形區域,藍色邊框代表View的內容。在上圖中,我們調用scrollTo/scrollBy把View向右滾動了一定距離。實際上,調用scrollBy/scrollTo方法只能實現View的內容的滾動,而View的四個位置參數是保持不變的。想一下我們平常使用ListView時,滾動的就是ListView的內容,而ListView本身在屏幕上的位置是不變的。上圖中,黑色左邊(即View的左邊緣)減去藍色左邊(即View的內容的左邊緣)即可得到mScrollX。由此我們還可以知道,向右滾動時mScrollX負的,向左滾動時mScrollX是正的。同理我們可以知道,向下滾動時,mScrollY是負的,向上滾動時,mScrollY是正的。

    經過以上的分析,我們了解到使用scrollTo/scrollBy方法實現View的滑動是很簡單直接的,那麼簡單的背後有什麼代價呢?代價就是滑動不是“彈性的”,彈性滑動指的是View的滑動應該是一個先加速再逐漸減速到停止的過程,這樣看起來很平滑,不會很突兀。scrollTo/scrollBy方法實現的滑動看起來就會很突兀,這樣的用戶體驗很不好。在解決這個問題之前,我們先來看看實現View滑動的其他方法。

 

(2)使用動畫來實現View的滑動

    使用動畫來實現View的滑動主要通過改變View的translationX和translationY參數來實現,使用動畫的好處在於滑動效果是平滑的。上面我們提到過,View的x、y參數決定View的當前位置,通過改變translationX和translationY,我們就可以改變View的當前位置。我們可以使用屬性動畫或者補間動畫來實現View的平移。

    首先,我們先來看一下如何使用補間動畫來實現View的平移。補間動畫資源定義如下(anim.xml):

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">

    <translate
        android:duration="100"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:interpolator="@android:anim/linear_interpolator"
        android:toXDelta="100"
        android:toYDelta="100"/>

</set>

 

 

    然後在onCreat方法中調用startAnimation方法即可。使用補間動畫實現View的滑動有一個缺陷,那就是移動的知識View的“影像”,這意味著其實View並未真正的移動,只是我們看起來它移動了而已。拿Button來舉例,假若我們通過補間動畫移動了一個Button,我們會發現,在Button的原來位置點擊屏幕會出發點擊事件,而在移動後的Button上點擊不會觸發點擊事件。

    接下來,我們看看如何用屬性動畫來實現View的平移。使用屬性動畫實現View的平移更加簡單,只需要以下一條語句:

ObjectAnimator.ofFloat(targetView, "translationX", 0, 100).setDuration(100).start();

 

    以上代碼即實現了使用屬性動畫把targetView在100ms內向右平移100px。使用屬性動畫的限制在於真正的屬性動畫只可以在Android 3.0+使用(一些第三方庫實現的兼容低版本的屬性動畫不是真正的屬性動畫),優點就是它可以真正的移動View而不是僅僅移動View的影像。

   經過以上的描述,使用屬性動畫實現View的滑動看起來是個不錯的選擇,而且一些View的復雜的滑動效果只有通過動畫才能比較方便的實現。

 

(3)通過改變布局參數來實現View的滑動

    通過改變布局參數來實現View的滑動的思想很簡單:比如向右移動一個View,只需要把它的marginLeft參數增大,向其它方向移動同理,只需改變相應的margin參數。還有一種比較拐彎抹角的方法是在要移動的View的旁邊預先放一個View(初始寬高設為0)。然後比如我們要向右移動View,只需把預先放置的那個View的寬度增大,這樣就把View“擠”到右邊了。代碼示例如下:

MarginLayoutParams params = (MarginLayoutParams) mButton.getLayoutParams();
params.leftMargin += 100;
mButton.requestLayout();

 

    以上代碼即實現了把mButton向右滑動100px。通過改變布局參數來實現的滑動效果也不是平滑的。

 

(4)使用Scroller來實現彈性滑動

    上面我們提到了使用scrollTo/scrollBy方法實現View的滑動效果不是平滑的,好消息是我們可以使用Scroller方法來輔助實現View的彈性滑動。使用Scroller實現彈性滑動的慣用代碼如下:

 Scroller scroller = new Scroller(mContext);
 
 private void smoothScrollTo(int dstX, int dstY) {
     int scrollX = getScrollX();
     int delta = dstX - scrollX;
     scroller.startScroll(scrollX, 0, delta, 0, 1000);
     invalidate();
 }
 
 @Override
 public void computeScroll() {
     if (scroller.computeScrollOffset()) {
         scrollTo(scroller.getCurrX(), scroller.getCurY());
         postInvalidate();
     }
 }

 

    我們來看一下以上的代碼。第4行中,我們獲取到View的mScrollX參數並存到scrollX變量中。然後在第5行計算要滑動的位移量。第6行調用了startScroll方法,我們來看看startScroll方法的源碼:

 public void startScroll(int startX, int startY, int dx, int dy, int duration) {  
     mMode = SCROLL_MODE;  
     mFinished = false;  
     mDuration = duration;  
     mStartTime = AnimationUtils.currentAnimationTimeMillis();  
     mStartX = startX;  
     mStartY = startY;  
     mFinalX = startX + dx;  
     mFinalY = startY + dy;  
     mDeltaX = dx;  
     mDeltaY = dy;  
     mDurationReciprocal = 1.0f / (float) mDuration;  
     
     mViscousFluidScale = 8.0f;  
    
     mViscousFluidNormalize = 1.0f;  
     mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);  
 }  

 

    從以上的源碼我們可以看到,startScroll方法中並沒有進行實際的滾動操作,而是把startX、startY、deltaX、deltaY等參數都保存了下來。那麼究竟怎麼實現View的滑動的呢?我們先回到Scroller慣用代碼。我們看到第7行調用了invalidate方法,這個方法會請求重繪View,這會導致View的draw的方法被調用,draw的方法內部會調用computeScroll方法。我們來看看第13行,調用了scrollTo方法,並傳入mScroller.getCurrX()和mScroller.getCurrY()方法作為參數。那麼獲取到的這兩個參數是什麼呢?這兩個參數是在第12行調用的computeScrollOffset方法中設置的,我們來看看這個方法中設置這兩個參數的相關代碼:

 public boolean computeScrollOffset() {
     ...
     int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
     if (timePassed < mDuration) {
         switch (mMode) {
             case SCROLL_MODE:
                 final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                 mCurrX = mStartX + Math.round(x * mDeltaX);
                 mCurrY = mStartY + Math.rounc(y * mDeltaY);
                 break;
         ...
         }
     }
     return true;
 }

 

    以上代碼中第8行和第9行設置的mCurrX和mCurrY即為以上scrollTo的兩個參數,表示本次滑動的目標位置。computeScrollOffset方法返回true表示滑動過程還未結束,否則表示結束。

    通過以上的分析,我們大概了解了Scroller實現彈性滑動的原理:invaldate方法會導致Viewdraw方法被調用,而draw會調用computeScroll方法,因此重寫了computeScroll方法,而computeScrollOffset方法會根據時間的流逝動態的計算出很小的一段時間應該滑動多少距離。也就是把一次滑動拆分成無數次小距離滑動從而實現“彈性滑動”。

 

3. 參考資料

    《Android開發藝術探索》

     

以上敘述中若有不清晰或是不准確的地方,希望大家指出:) 

 

 

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