SpringAnimation弹簧动画
Android最近更新了Support Library包,在25.3中新增了一个动画效果,名为SpringAnimation(弹簧动画)。
使用步骤一:
需要25的编译环境,同时要求最低版本为16及以上,所以使用上还是有一些限制。如果有特殊需要,可以通过tools:overrideLibrary
来做对应的兼容适配。
build.xml:
minSdkVersion 16
compileSdkVersion 25
compile 'com.android.support:support-dynamic-animation:25.3.0'
导入之后就能看到对应的动画类SpringAnimation
android.support.animation.SpringAnimation
使用步骤二:
SpringAnimation提供了两个构造方法:
public SpringAnimation(View v, ViewProperty property)
public SpringAnimation(View v, ViewProperty property, float finalPosition)
分别是操作对应的View,对应的变化属性及最终的位置。
ViewProperty从现有支持的看,包括(Z轴的支持需要API >= 21):
TRANSLATION_X
TRANSLATION_Y
TRANSLATION_Z
SCALE_X
SCALE_Y
ROTATION
ROTATION_X
ROTATION_Y
X
Y
Z
ALPHA
SCROLL_X
SCROLL_Y
然后在SpringAnimation中有一个SpringForce对象,负责对应的变量设置及位置计算。其中包括两个个关键变量
-
Stiffness
刚度(劲度/弹性),刚度越大,形变产生的里也就越大,体现在效果上就是运动越快 -
DampingRatio
阻尼系数,系数越大,动画停止的越快。从理论上讲分为三种情况 Overdamped过阻尼(ζ > 1)、Critically damped临界阻尼(ζ = 1)、Underdamped欠阻尼状态(0<ζ <1)。
不过估计看到这,大家肯定还是一脸懵逼。所以,这里先稍微解释一下弹簧跟阻尼的概念:
- 从原理上看,当弹簧处于平衡位置(不受力的自然位置)时,如果受到挤压或者拉伸后,当放手后弹簧会恢复原状。
- 弹回来的过程是「弹性势能」转化成「动能」,当重新恢复到平衡点时,因为还有速度,所以会继续向前运动,这个时候「动能」又对应的转化为「弹性势能」。
- 如果能量转换没有损失,将不断的伸长缩短,也就是不断的来来回回。
- 但能量转换是有损失的,所以缩回来的距离会不断缩小,弹出去的距离也一样,这个每次损耗缩小的距离比,其实就是阻尼。
这样解释后,大家对于关键的参数应该有了一定的了解。那接下来我们就继续实战。
使用步骤三:
尝试模仿Dribbble上的一个作品,设计效果图如下:
分析整个动画效果,主体上分为两个部分:
- 各元素从下往上移动的弹簧效果;SpringAnimation可以实现对应的效果。
- 移动过程中的透明度变化;ValueAnimation用来实现透明度Alpha从0到1的变化,从而实现淡入的感觉。
然后简单的说明下SpringAnimation的使用方法:
SpringAnimation signUpBtnAnimY = new SpringAnimation(mSignUpBtn,SpringAnimation.TRANSLATION_Y,0);
signUpBtnAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
signUpBtnAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
signUpBtnAnimY.setStartVelocity(10000);
signUpBtnAnimY.start();
之前介绍SpringAnimation的时候聊过了几个影响实际效果的因素,这里再根据具体使用来说明下:
(1) DampingRatio阻尼系数,通过getSpring().setDampingRatio
方法来设置。
从效果上看,ζ = 0的时候就是无限来回运动,0< ζ <1的时候会出现来回减弱的振荡最后停止,ζ >= 1的时候会在靠近原位置的时候提前减速后停止。
在SpringForce中有对应的常量设置:
/**
* Damping ratio for a very bouncy spring. Note for under-damped springs
* (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.
*/
public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f;
/**
* Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring
* force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio,
* the more bouncy the spring.
*/
public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f;
/**
* Damping ratio for a spring with low bounciness. Note for under-damped springs
* (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness.
*/
public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f;
/**
* Damping ratio for a spring with no bounciness. This damping ratio will create a critically
* damped spring that returns to equilibrium within the shortest amount of time without
* oscillating.
*/
public static final float DAMPING_RATIO_NO_BOUNCY = 1f;
(2) Stiffness刚度,通过getSpring().setStiffness
方法来设置。
在SpringForce中有对应的常量设置:
/**
* Stiffness constant for extremely stiff spring.
*/
public static final float STIFFNESS_HIGH = 10_000f;
/**
* Stiffness constant for medium stiff spring. This is the default stiffness for spring force.
*/
public static final float STIFFNESS_MEDIUM = 1500f;
/**
* Stiffness constant for a spring with low stiffness.
*/
public static final float STIFFNESS_LOW = 200f;
/**
* Stiffness constant for a spring with very low stiffness.
*/
public static final float STIFFNESS_VERY_LOW = 50f;
(3) StartVelocity开始速度,单位是px/second. 正数是弹簧收缩的方向,负数则相反。
根据效果要求设置完对应的参数后,调用start方法后即可执行对应的动画。
需要注意的是SpringAnimation动画是无法设置执行时间的,所以如果有同期需要执行的动画,可以评估对应的执行时间或者通过updateListener来做对应的处理。
简略的实现
public class MainActivity extends AppCompatActivity {
private Button mSignUpBtn;
private ImageView mLeftLogoImg;
private ImageView mRightLogoImg;
private TextView mDescTitleTextView;
private LinearLayout mLettersLayout;
private LinearLayout mSignInLayout;
private ArrayList<SpringAnimation> mLetterAnims;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// hide the status ui.
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
// get the screen height.
DisplayMetrics dm = getResources().getDisplayMetrics();
int screenHeight = dm.heightPixels;
// letters about 'Converse'
mLettersLayout = (LinearLayout) findViewById(R.id.letter_layout);
mLetterAnims = new ArrayList<>();
for (int i = 0; i < mLettersLayout.getChildCount(); i++) {
View letterView = mLettersLayout.getChildAt(i);
letterView.setTranslationY(screenHeight);
SpringAnimation letterAnimY = new SpringAnimation(letterView, SpringAnimation.TRANSLATION_Y, 0);
letterAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
letterAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
mLetterAnims.add(letterAnimY);
}
// text about 'Native messaging'
mDescTitleTextView = (TextView) findViewById(R.id.desc_title_textview);
mDescTitleTextView.setTranslationY(500f);
mDescTitleTextView.setAlpha(0f);
final SpringAnimation descTitleAnimY = new SpringAnimation(mDescTitleTextView, DynamicAnimation.TRANSLATION_Y, 0);
descTitleAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
descTitleAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
final ValueAnimator descTitleAlphaAnim = ObjectAnimator.ofFloat(0f, 1f);
descTitleAlphaAnim.setDuration(300);
descTitleAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mDescTitleTextView.setAlpha((Float) valueAnimator.getAnimatedValue());
}
});
// the button of sign up
mSignUpBtn = (Button) findViewById(R.id.sign_up_btn);
mSignUpBtn.setTranslationY(500f);
final SpringAnimation signUpBtnAnimY = new SpringAnimation(mSignUpBtn, SpringAnimation.TRANSLATION_Y, 0);
signUpBtnAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
signUpBtnAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
// the bottom text about 'Have an account? sign in'
mSignInLayout = (LinearLayout) findViewById(R.id.signin_layout);
mSignInLayout.setTranslationY(500f);
final SpringAnimation signInLayoutAnimY = new SpringAnimation(mSignInLayout, SpringAnimation.TRANSLATION_Y, 0);
signInLayoutAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
signInLayoutAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
// top logo by left
mLeftLogoImg = (ImageView) findViewById(R.id.left_logo_imageview);
mLeftLogoImg.setTranslationY(400f);
mLeftLogoImg.setAlpha(0f);
final SpringAnimation leftLogoAnimY = new SpringAnimation(mLeftLogoImg, SpringAnimation.TRANSLATION_Y, 0);
leftLogoAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
leftLogoAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
leftLogoAnimY.setStartVelocity(-2000);
// top logo by right
mRightLogoImg = (ImageView) findViewById(R.id.right_logo_imageview);
mRightLogoImg.setTranslationY(400f);
mRightLogoImg.setAlpha(0f);
final SpringAnimation rightLogoAnimY = new SpringAnimation(mRightLogoImg, SpringAnimation.TRANSLATION_Y, 0);
rightLogoAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
rightLogoAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
rightLogoAnimY.setStartVelocity(-2000);
final ValueAnimator logoAlphaAnim = ObjectAnimator.ofFloat(0f, 1f);
logoAlphaAnim.setDuration(600);
logoAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mLeftLogoImg.setAlpha((Float) valueAnimator.getAnimatedValue());
mRightLogoImg.setAlpha((Float) valueAnimator.getAnimatedValue());
}
});
mRightLogoImg.postDelayed(new Runnable() {
@Override
public void run() {
leftLogoAnimY.start();
mRightLogoImg.postDelayed(new Runnable() {
@Override
public void run() {
rightLogoAnimY.start();
}
}, 150);
mDescTitleTextView.postDelayed(new Runnable() {
@Override
public void run() {
descTitleAlphaAnim.setStartDelay(100);
descTitleAlphaAnim.start();
descTitleAnimY.start();
signUpBtnAnimY.start();
signInLayoutAnimY.start();
}
}, 300);
for (final SpringAnimation letterAnim : mLetterAnims) {
mLettersLayout.postDelayed(new Runnable() {
@Override
public void run() {
letterAnim.start();
}
}, 12 * mLetterAnims.indexOf(letterAnim));
}
logoAlphaAnim.start();
}
}, 1000);
}
}
最终实现的效果如下图,实现的有点粗糙,希望能给大家带来解决需求上的新思路。