Scroller實現View的平滑滑動

        在之前介紹的關於View滑動的內容時,關於使用使用Scroller進行View的平滑滑動沒有介紹,今天在這裏補充一下。關於爲什麼使用Scroller滑動,這裏就不多介紹,主要是因爲使用scrollTo()或者scrollBy()都是瞬間完成,用戶體驗不是太好,當然滑動最終的實現都是通過scrollTo()實現,所以,你也可以通過不斷移動一小段距離再加上一個Timer進行控制,也可以實現平滑移動的效果,具體可以參見Android中實現滑動(中)----實現(2)。下面就來看看如何使用Scroller。

1,使用步驟

這裏我們直接進入主題,先給出Scroller的使用步驟,然後給出示例代碼和效果圖,最後在結合源碼簡單分析一下。使用步驟如下:

  • 自定義一個View繼承你想實現平滑滑動的控件
  • 添加一個Scroller成員變量,並在構造方法中初始化
  • 重寫View的computeScroll()方法
  • 定義外部訪問的用於調用滑動實現的接口

有一點抽象,下面我們就結合具體的示例說一下,首先我們自定義各一個SelfButton繼承至Button,並在內部實現了上述四個步驟:

package com.hfut.operationscroll;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.Scroller;

/**
 * author:why
 * created on: 2019/5/25 13:31
 * description:用Scroller實現View 的平滑滑動
 */
public class SelfButton extends android.support.v7.widget.AppCompatButton {

    Scroller scroller;
    public SelfButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        scroller=new Scroller(context);

    }

    /**
     * 第三步:重寫computeScroll()方法
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        if(scroller.computeScrollOffset()){
            ((View)getParent()).scrollTo(scroller.getCurrX(),scroller.getCurrY());
            invalidate();//重繪,在重繪調用draw()方法中,內部會調用View的computeScroll()方法
        }
    }

    /**
     * 第四步:對外調用接口,可以封裝成接口
     * @param destX:滑動Dest X
     * @param destY:滑動Dest Y
     * @param duration:滑動時間
     */
    public void scrollerMove(int destX,int destY,int duration){
        int scrollX=getScrollX();
        int scrollY=getScrollY();
        int moveDistX=destX-scrollX;
        int moveDistY=destX-scrollY;
        scroller.startScroll(scrollX,scrollY,moveDistX,moveDistY,duration);
        invalidate();
    }
}

然後我們在業務層調用一下看看效果:

 button.scrollerMove(-400, -400,5000);

可以看到我們使用scroller再加上上面說的四步,成功實現view的平滑滑動,那麼究竟是Scroller究竟是什麼,怎麼實現的了,下面我們就來簡單分析一下。

 

2,Scroller實現View平滑滑動分析

首先,我們使用Scroller的指導它究竟是什麼,那麼最好的當然還是看官方文檔介紹:

* <p>This class encapsulates scrolling. You can use scrollers ({@link Scroller}
* or {@link OverScroller}) to collect the data you need to produce a scrolling
* animation&mdash;for example, in response to a fling gesture. Scrollers track
* scroll offsets for you over time, but they don't automatically apply those
* positions to your view. It's your responsibility to get and apply new
* coordinates at a rate that will make the scrolling animation look smooth.</p>

這是一個封裝了滾動的類,你可以通過滾動條來收集你想要產生滾動動畫的數據,比如,響應一個手勢動作;滾動條會實時獲取滾動的偏移量,但是它們不負責把這些數據應用到你的view上,你需要自己吧這些數據應用到你的view上實現一個平滑滾動動畫的效果。

上面是我自己簡單翻譯的,感覺比較抽象哈,其實一點都不難,我們就結合上面給的示例說明一下上面意思

  • 首先Scroller它是一個封裝了滾動類,你可以通過它來收集數據
  • 究竟如何收集數據了,那我們就需要轉到Scroller源碼中一探究竟了。首先我們知道獲取數據的接口如下(在重寫的computeScroll()中):
scroller.getCurrX()
scroller.getCurrY()

我們跟到Scroller源碼中看一下(只給出一個,另一個類似):

   /**
     * Returns the current X offset in the scroll. 
     * 
     * @return The new X offset as an absolute distance from the origin.
     */
    public final int getCurrX() {
        return mCurrX;
    }

這裏只是返回了一個內部變量的值,那麼它的賦值邏輯我們在哪個調用的接口中實現的了。我們在重寫獲取這些數值之前還調用了Scroller的一個接口,那就是computeScrollOffset(),下面就來看看它的邏輯代碼:

 /**
     * Call this when you want to know the new location.  If it returns true,
     * the animation is not yet finished.
     */ 
    public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        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.round(x * mDeltaY);
                break;
            case FLING_MODE:
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                float distanceCoef = 1.f;
                float velocityCoef = 0.f;
                if (index < NB_SAMPLES) {
                    final float t_inf = (float) index / NB_SAMPLES;
                    final float t_sup = (float) (index + 1) / NB_SAMPLES;
                    final float d_inf = SPLINE_POSITION[index];
                    final float d_sup = SPLINE_POSITION[index + 1];
                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                }

                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
                
                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);
                
                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                // Pin to mMinY <= mCurrY <= mMaxY
                mCurrY = Math.min(mCurrY, mMaxY);
                mCurrY = Math.max(mCurrY, mMinY);

                if (mCurrX == mFinalX && mCurrY == mFinalY) {
                    mFinished = true;
                }

                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }

這個就是實現在Scroller開始滾動後(startScroll())計算數據的邏輯,其中startScroll()的邏輯如下:

/**
     * Start scrolling by providing a starting point and the distance to travel.
     * The scroll will use the default value of 250 milliseconds for the
     * duration.
     * 
     * @param startX Starting horizontal scroll offset in pixels. Positive
     *        numbers will scroll the content to the left.
     * @param startY Starting vertical scroll offset in pixels. Positive numbers
     *        will scroll the content up.
     * @param dx Horizontal distance to travel. Positive numbers will scroll the
     *        content to the left.
     * @param dy Vertical distance to travel. Positive numbers will scroll the
     *        content up.
     */
    public void startScroll(int startX, int startY, int dx, int dy) {
        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
    }

    /**
     * Start scrolling by providing a starting point, the distance to travel,
     * and the duration of the scroll.
     * 
     * @param startX Starting horizontal scroll offset in pixels. Positive
     *        numbers will scroll the content to the left.
     * @param startY Starting vertical scroll offset in pixels. Positive numbers
     *        will scroll the content up.
     * @param dx Horizontal distance to travel. Positive numbers will scroll the
     *        content to the left.
     * @param dy Vertical distance to travel. Positive numbers will scroll the
     *        content up.
     * @param duration Duration of the scroll in milliseconds.
     */
    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;
    }
  • 收集之後,如果你不做任何操作,Scroller本身也不會爲你的View和這些data建立聯繫,那我們就需要重寫computeScroll()方法自己應用這些數據
 ((View)getParent()).scrollTo(scroller.getCurrX(),scroller.getCurrY());
 invalidate();//重繪,在重繪調用draw()方法中,內部會調用View的computeScroll()方法
  • 就這樣,Scroller內部獲取數據通過接口拋出來,我們拿到之後應用到View的滑動動畫上就可以了。

所以整個流程梳理下來就是如下這樣:

  • 我們開啓Scroller的startScroll()實現一些狀態的賦值,比如mFinished等
  • 我們在自定義View的重寫的computeScroll()方法中調用Scroller的computeScrollerOffset()接口計算滑動數據,然後通過Scroller的getCurrX()等接口獲取實時計算的數據
  • 拿到數據之後,我們需要根據自己的需要來使用這些數據,我們這裏想要實現一個滑動的效果,所以直接調用了scrollTo()來接收這些數據,最後通過invalidate()方法實現view的重繪
  • 在View重繪的過程中,必然需要調用draw()方法,而在draw()方法中,又會調用computeScroll()方法,就這樣實現了一個平滑動畫的效果,直至結束(以下條件不滿足,邏輯來自Scroller中的computeScrollOffset())。
   if (timePassed < mDuration)

至此,關於Scroller實現View的平滑滑動的實現以及分析就結束了。如果想了解View其他滑動實現,歡迎閱讀

注:歡迎掃碼關注

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章