實現一個帶下拉彈簧動畫的 ScrollView

NanBox發表於2017-12-13

在剛推出的 Support Library 25.3.0 裡面新增了一個叫 SpringAnimation 的動畫,也就是彈簧動畫。要是用它來做一個滑動控制元件下拉回彈的效果,應該不錯吧。

SpringAnimation

開始之前,別忘了在 app 的 build.gradle 加上:

compile 'com.android.support:appcompat-v7:25.3.0'
compile 'com.android.support:design:25.3.0'
compile 'com.android.support:support-dynamic-animation:25.3.0'

然後我們看看 SpringAnimation 的基本用法,首先是它的構造方法:

public SpringAnimation(View v, ViewProperty property, float finalPosition) {
    super(v, property);
    mSpring = new SpringForce(finalPosition);
    setSpringThreshold();
}
複製程式碼

看命名可以大概猜到引數的意義了:

  • v - 要執行動畫的控制元件
  • property - 動畫的性質,可以選擇平移、縮放、旋轉等
  • finalPosition - 動畫結束時,控制元件所在位置的座標偏移量

這裡實現的滑動控制元件是上下滑動的,所以我們這樣來獲取 SpringAnimation :

springAnim = new SpringAnimation(this, SpringAnimation.TRANSLATION_Y, 0);
複製程式碼

SpringAnimation 裡面有兩個比較重要的屬性,分別是:

  • Stiffness - 剛度,值越大回彈的速度越快,類似於勁度係數,預設值是 1500f
  • DampingRatio - 阻尼,值越小,回彈後,動畫來回的次數越多,就是更有「DUANG」的感覺,預設值是 0.5f

通過

springAnim.getSpring().setStiffness(float stiffness) 
複製程式碼

springAnim.getSpring().setDampingRatio(float dampingRatio) 
複製程式碼

來設定上面兩個屬性。

再呼叫 springAnim.start() 就可以開始動畫啦。

SpringScrollView

我們自定義一個 SpringScrollView 繼承 NestedScrollView,重寫 onTouchEvent 方法讓它有回彈的效果:

@Override
public boolean onTouchEvent(MotionEvent e) {
    switch (e.getAction()) {
        case MotionEvent.ACTION_MOVE:
            if (getScrollY() <= 0) {
                //頂部下拉
                if (startDragY == 0) {
                    startDragY = e.getRawY();
                }
                if (e.getRawY() - startDragY > 0) {
                    setTranslationY((e.getRawY() - startDragY) / 3);
                    return true;
                } else {
                    springAnim.cancel();
                    setTranslationY(0);
                }
            } 
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            if (getTranslationY() != 0) {
                springAnim.start();
            }
            startDragY = 0;
            break;
    }
    return super.onTouchEvent(e);
}
複製程式碼

簡單解釋一下哈。

當 ScrollView 在頂部時,記錄下手指所在的 y 軸位置。在頂部並且是往下滑動的時候,給 ScrollView 設定一個縱向的偏移。之所以除以 3,是為了讓控制元件有種要用力才能拖動的感覺。

在頂部的時候如果是往上滑動,則把動畫效果取消,把控制元件位置復原,否則可能出現控制元件一直偏移的情況。

最後當手指抬起時,執行彈簧動畫就好了。

為什麼這裡用 getRawY() 獲取座標,而不是用 getY() 來獲取。因為 getY() 是相對於控制元件的座標,當設定了 TranslationY 之後會改變它的值,也就是在滑動的時候 getY() 的值是不連續的,會出現卡頓的現象。而 getRawY() 是相對於螢幕的位置,管你控制元件怎麼動,螢幕都是固定的。

下拉回彈的效果就已經完成了。對了,我們順便把底部上拉的回彈也做一下唄。由於ScrollView只有一個子佈局,所以可以通過

getScrollY() + getHeight()) >= getChildAt(0).getMeasuredHeight()
複製程式碼

判斷是否滑動到了底部。

完整程式碼如下:

public class SpringScrollView extends NestedScrollView {

    private float startDragY;
    private SpringAnimation springAnim;

    public SpringScrollView(Context context) {
        this(context, null);
    }

    public SpringScrollView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SpringScrollView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        springAnim = new SpringAnimation(this, SpringAnimation.TRANSLATION_Y, 0);
        //剛度 預設1200 值越大回彈的速度越快
        springAnim.getSpring().setStiffness(800.0f);
        //阻尼 預設0.5 值越小,回彈之後來回的次數越多
        springAnim.getSpring().setDampingRatio(0.50f);
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                if (getScrollY() <= 0) {
                    //頂部下拉
                    if (startDragY == 0) {
                        startDragY = e.getRawY();
                    }
                    if (e.getRawY() - startDragY > 0) {
                        setTranslationY((e.getRawY() - startDragY) / 3);
                        return true;
                    } else {
                        springAnim.cancel();
                        setTranslationY(0);
                    }
                } else if ((getScrollY() + getHeight()) >= getChildAt(0).getMeasuredHeight()) {
                    //底部上拉
                    if (startDragY == 0) {
                        startDragY = e.getRawY();
                    }
                    if (e.getRawY() - startDragY < 0) {
                        setTranslationY((e.getRawY() - startDragY) / 3);
                        return true;
                    } else {
                        springAnim.cancel();
                        setTranslationY(0);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (getTranslationY() != 0) {
                    springAnim.start();
                }
                startDragY = 0;
                break;
        }
        return super.onTouchEvent(e);
    }

}
複製程式碼

最後看看效果吧:

實現一個帶下拉彈簧動畫的 ScrollView

同樣的思路也可以用在別的滑動控制元件裡面。

妥妥的。

原始碼地址

相關文章