在剛推出的 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();
}
看命名可以大概猜到參數(shù)的意義了:
- v - 要執(zhí)行動畫的控件
- property - 動畫的性質,可以選擇平移、縮放、旋轉等
- finalPosition - 動畫結束時,控件所在位置的坐標偏移量
這里實現(xiàn)的滑動控件是上下滑動的,所以我們這樣來獲取 SpringAnimation :
springAnim = new SpringAnimation(this, SpringAnimation.TRANSLATION_Y, 0);
SpringAnimation 里面有兩個比較重要的屬性,分別是:
- Stiffness - 剛度,值越大回彈的速度越快,類似于勁度系數(shù),默認值是 1500f
- DampingRatio - 阻尼,值越小,回彈后,動畫來回的次數(shù)越多,就是更有「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,是為了讓控件有種要用力才能拖動的感覺。
在頂部的時候如果是往上滑動,則把動畫效果取消,把控件位置復原,否則可能出現(xiàn)控件一直偏移的情況。
最后當手指抬起時,執(zhí)行彈簧動畫就好了。
為什么這里用 getRawY() 獲取坐標,而不是用 getY() 來獲取。因為 getY() 是相對于控件的坐標,當設置了 TranslationY 之后會改變它的值,也就是在滑動的時候 getY() 的值是不連續(xù)的,會出現(xiàn)卡頓的現(xiàn)象。而 getRawY() 是相對于屏幕的位置,管你控件怎么動,屏幕都是固定的。
下拉回彈的效果就已經(jīng)完成了。對了,我們順便把底部上拉的回彈也做一下唄。由于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 值越小,回彈之后來回的次數(shù)越多
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);
}
}
最后看看效果吧:
同樣的思路也可以用在別的滑動控件里面。
妥妥的。