Android中弹簧动画的那些事 - SpringAnimation
SpringAnimation弹簧动画
Android最近更新了Support Library包,在25.3中新增了一个动画效果,名为SpringAnimation(弹簧动画)。
使用步骤一:
需要25的编译环境,同时要求最低版本为16及以上,所以使用上还是有一些限制。如果有特殊需要,可以通过tools:overrideLibrary
来做对应的兼容适配。
build.xml:minSdkVersion 16compileSdkVersion 25compile '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_XTRANSLATION_YTRANSLATION_ZSCALE_XSCALE_YROTATIONROTATION_XROTATION_YXYZALPHASCROLL_XSCROLL_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 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); }}
最终实现的效果如下图,实现的有点粗糙,希望能给大家带来解决需求上的新思路。
原文链接: Android中弹簧动画的那些事 - SpringAnimation
更多相关文章
- Android(安卓)AnimationDrawable运行的几种方式(转)
- Android开发从初级到高级学习路线
- android 自定义Toast增加点击事件、Toast弹出隐藏动画、Toast宽
- Animation用法_animation动画效果
- [置顶] 【Android】 给我一个Path,还你一个酷炫动画
- Android:使用ViewFlipper实现上下滚动消息
- Android(安卓)自定义 MarqueeView 实现跑马灯 —— 原理篇
- Android4.0.3修改启动动画和开机声音
- Android系统移植与调试之------->如何修改Android启动动画和开机