在剛推出的 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);
}
}
複製程式碼
最後看看效果吧:
同樣的思路也可以用在別的滑動控制元件裡面。
妥妥的。