SpringAnimation 类是最近(25.3.0版本)才添加在支持库中的一个类,它主要是为了让实现弹性动画变得更加方便,其实facebook在很久以前的Rebound库就实现了这样的动画,而且效果非常好,不过现在有官方支持肯定是更好了。本文先来看看SpringAnimation的基本用法,然后再将它和Rebound做一些比较。

SpringAnimation类介绍

SpringAnimation类位于android.support.animation包之下,别和属性动画混淆了,属性动画的包是android.animation.Animator,看来以后对动画的临时增改都会放在android.support.animation里面了。跟SpringAnimation一起放出的类还有:

DynamicAnimation<T extends DynamicAnimation<T>>  这是物理动画类的基类
DynamicAnimation.ViewProperty ViewProperty持有一个View的属性。
SpringAnimation SpringAnimation是由一个SpringForce驱动的动画。
SpringForce Spring Force defines the characteristics of the spring being used in the animation. 

其中SpringAnimation类是DynamicAnimation类的子类,而DynamicAnimation的意思是符合动力学的动画,也就是物理动画。 

为什么要使用SpringAnimation?

我们已经有万能的属性动画了, 使用 BounceInterpolator 或者 OvershootInterpolator 作为插值器不是一样可以弹来弹去么,为什么还需要SpringAnimation呢?

主要是因为用它实现弹性动画非常简单,而且BounceInterpolator 或者 OvershootInterpolator 的轨迹并不符合物理学上的弹跳效果。

SpringAnimation的基本用法

先引入必要的包:

compile 'com.android.support:support-dynamic-animation:25.3.0'

使用SpringAnimation非常简单,几行代码

SpringForce springForce = new SpringForce(0)
        .setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY)
        .setStiffness(SpringForce.STIFFNESS_VERY_LOW);
final SpringAnimation anim = new SpringAnimation(view ,SpringAnimation.TRANSLATION_Y).setSpring(springForce);
anim.start();

就是这么简单。

对以上代码的解释

我们首先定义了一个用于设置弹性参数的 SpringForce 对象,其构造参数中的 0 表示被作用对象的最终位置。

setDampingRatio(float dampingRatio)方法设置弹性阻尼,dampingRatio越大,摆动次数越少,当到1的时候完全不摆动,注意它体验出来的最明显的特征是摆动次数这个概念,SpringForce类中预定义了几个dampingRatio常量:

public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f;
public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f;
public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f;
public static final float DAMPING_RATIO_NO_BOUNCY = 1f;

setStiffness(float stiffness)方法设置弹性的生硬度,stiffness值越小,弹簧越容易摆动,摆动的时间越长,反之摆动时间越短,注意它体验出来的最明显的特征是摆动时间这个概念。SpringForce类中预定义了几个stiffness的常量:

public static final float STIFFNESS_HIGH = 10_000f;
public static final float STIFFNESS_MEDIUM = 1500f;
public static final float STIFFNESS_LOW = 200f;
public static final float STIFFNESS_VERY_LOW = 50f;

定义好了SpringForce对象,我们就可以定义SpringAnimation了,将被作用的view和变化属性传递给SpringAnimation的构造函数,然后调用setSpring设置我们定义好了的SpringForce对象。最后调用SpringAnimation的start()方法开始动画。

前面的代码中,SpringAnimation构造方法只有两个参数:

public SpringAnimation(View v, ViewProperty property)

其实还有一个三个参数的构造方法

public SpringAnimation(View v, ViewProperty property, float finalPosition)

这个构造方法的第三个参数其实就是用于创建SpringForce对象,一般来讲使用这个构造方法的时候我们要设置阻尼和生硬度是先调用 getSpring()获得SpringForce,然后再调用setDampingRatio和setStiffness。

所以前面的代码也可以这么写:

final SpringAnimation anim = new SpringAnimation(view ,SpringAnimation.TRANSLATION_Y,0);
anim.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
anim.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
anim.start();

那么问题来了,我们可以通过SpringForce设置被作用对象(一般就是一个view)的最终位置,那么怎么设置起始位置呢?默认起始位置就是view属性的当前值。如果你要设置可以调用SpringAnimation的setStartValue(float startValue)方法,比较怪异的是,起始位置不是由SpringForce决定的(而最终位置却是)。

最后值得一提的就是SpringAnimation可以作用的属性:

TRANSLATION_X
TRANSLATION_Y
TRANSLATION_Z
SCALE_X
SCALE_Y
ROTATION
ROTATION_X
ROTATION_Y
X
Y
Z
ALPHA
SCROLL_X
SCROLL_Y

比如我们要改变Y轴上的位置就使用

new SpringAnimation(view ,SpringAnimation.TRANSLATION_Y);

这里的SpringAnimation.TRANSLATION_Y也可以换成 DynamicAnimation.TRANSLATION_Y。

小球弹跳的例子

定义布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff">
    <Button
        android:id="@+id/bounce"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳"
        android:layout_marginBottom="63dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
    <ImageView
        android:id="@+id/ball"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/bounce"
        android:scaleType="centerCrop"
        android:layout_centerHorizontal="true"
        android:src="@drawable/ball" />
</RelativeLayout>

布局定义好了之后,我们准备使用SpringAnimation让这个小球跳起来

package com.jcodecraeer.springanimationdemo;
import android.content.Intent;
import android.support.animation.DynamicAnimation;
import android.support.animation.SpringAnimation;
import android.support.animation.SpringForce;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class BallActivity extends AppCompatActivity {
    private Button buttonBounce ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ball);
        buttonBounce = (Button)findViewById(R.id.bounce);
        final View ball = findViewById(R.id.ball);
        SpringForce spring = new SpringForce(0)
                .setDampingRatio(0.05f)
                .setStiffness(SpringForce.STIFFNESS_VERY_LOW);
        final SpringAnimation anim = new SpringAnimation(ball ,SpringAnimation.TRANSLATION_Y).setSpring(spring);
        buttonBounce.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                anim.cancel();
                anim.setStartValue(-700);
                anim.start();
            }
         });
    }
}

效果如下:

Untitled.gif

与Rebound的比较

SpringAnimation是直接作用于一个view,而Rebound是对数值区间进行采样,并不直接改变view的属性。Rebound的使用方法更类似ValueAnimator。

下面是Rebound的代码示例:

// Create a system to run the physics loop for a set of springs.
SpringSystem springSystem = SpringSystem.create();
// Add a spring to the system.
Spring spring = springSystem.createSpring();
// Add a listener to observe the motion of the spring.
spring.addListener(new SimpleSpringListener() {
  @Override
  public void onSpringUpdate(Spring spring) {
    // You can observe the updates in the spring
    // state by asking its current value in onSpringUpdate.
    float value = (float) spring.getCurrentValue();
    float scale = 1f - (value * 0.5f);
    myView.setScaleX(scale);
    myView.setScaleY(scale);
  }
});
// Set the spring in motion; moving from 0 to 1
spring.setEndValue(1);

Rebound这种处理方式的好处是,如果要同时改变多个属性,会方便很多,而SpringAnimation的优势是不用关心数值上的处理,你只要告诉我最终值就行了。

为了对比Rebound和SpringAnimation的效果(流畅度),我做了一个demo,分别用Rebound和SpringAnimation实现Rebound官网上的示例项目,你可以下载下来运行,比较一下。两个例子都有滑杆控制阻尼之类的参数,我调节了多次参数,得出的结论是两者效果都差不多。

Untitled.gif

所以你会用哪个呢?tell me。

本文的所有例子demo在这里https://github.com/jianghejie/SpringAnimationDemo  。