PhysicsBasedAnimation学习记录
一、前言
1.概述
Google I/O'17推出了许多新的特性,在动画这一块又有新的API供开发者使用,在动画API中引入了DynamicAnimation,开发者可以使用新的API创建更加动态化的动画。
2.是什么
Physics-based Animation,翻译过来就是基于物理的动画。在日常生活中当一个事物发生变化的时候,物理性的过渡或者说符合自然性的过渡,更容易让人们感知察觉,同样,更自然、不间断、有良好的发展趋势的动画会给我们带来更好的用户体验。Physics-based Animations是根据物理学的基本原理构建的动画,动画由力产生,当力趋于平衡时动画处于静止。
3.优点
android提供的其他动画由固定的时间间隔和变化的动画值驱动。在运行过程中改变动画而不引入任何视觉破坏是非常具有挑战性的。
使用Physics-based Animations api创建的动画可以追踪速度,在运动过程中动态地改变动画的目标值,正确规划路线,使动画看起来更加自然。
自然的
动画更灵活,模仿实时移动
矫正方向
动画在目标变化时保持动力,以更平稳的结束动画
减少视觉冲击
动画显示更加敏感和平滑,并减少整体的视觉中断。
二、使用
1.引入
在app的build.gradle文件中加入:
compile 'com.android.support:support-dynamic-animation:26.0.0-beta2'
在项目的build.gradle文件中加入:
allprojects {
repositories {
jcenter()
maven {
url 'https://maven.google.com'
}
}
}
注意:如果不加入这个,引入Physics-based Animation就会失败。
2.Fling Animation
FlingAnimation flingAnimation = new FlingAnimation(img, DynamicAnimation.X); flingAnimation.setStartVelocity(500f);//设置初速度 flingAnimation.setFriction(0.5f);//设置摩擦力 flingAnimation.start();//开始动画
FlingAnimationd的构造函数中img表示的是动画的view,DynamicAnimation.X表示动画的类型。
使用setStartVelocity()方法赋予一个大于0的初速度,否则它不会动。
Friction是摩擦力,在现实生活中如果一个物体保持一个速度在无摩擦力的情况下会一直运动下去。摩擦系数的值越大,说明摩擦力越大,动画越快停下来,默认值为1。
调用start()方法开始动画。
3.Spring Animation
Spring Animation弹性动画模仿弹簧的变化过程,基于施加在每个对象上的弹性力来计算值和速度。
SpringAnimation springAnimation = new SpringAnimation(img, DynamicAnimation.X); springAnimation.setStartVelocity(2000);//设置初速度 SpringForce springForce = new SpringForce(); springForce.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY);//设置弹性阻尼,值越大,反弹次数越小 springForce.setStiffness(SpringForce.STIFFNESS_LOW);//设置生硬度,可以理解成药恢复成未拉伸状态所需的时间 springForce.setFinalPosition(img.getX());//指定最后静止时的位置 springAnimation.setSpring(springForce); springAnimation.start();
弹性动画支持以下View属性:
ALPHA:表示视图中的Alpha透明度。默认值为1(不透明),值为0表示完全透明(不可见)。
TRANSLATION属性:TRANSLATION_X,TRANSLATION_Y和TRANSLATION_Z。
ROTATION、ROTATION_X和ROTATION_Y:这些属性控制旋转点在2D(rotation属性)和3D周围旋转。
SCROLL_X和SCROLL_Y:这些属性指示源左侧和顶部边缘的滚动偏移(以像素为单位)。它也表示了页面滚动的位置。
SCALE_X和SCALE_Y、X、Y和Z:这些用于描述视图在其容器中的最终位置的基本使用程序属性。
和FlingAnimation一样,创建完SpringAnimation后我们需要设置初速度,接着创建了一个SpringForce实例,并设置了DampingRatio(弹性阻尼比)和Stiffness(刚性)。
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 = 1.0F;
默认设置为DAMPING_RATIO_MEDIUM_BOUNCY。
阻尼比描述弹簧振荡的阻力大小:
当阻尼比大于1时发生过阻,它使对象快速返回到休息位置。
当阻尼比等于1时发生临界阻尼。它使对象在最短的时间内返回到休息位置。
当阻尼比小于1时发生欠阻尼。它通过传递静止位置使物体多次过冲,然后逐渐达到静止位置。
当阻尼比等于零时,就会发生阻尼。它让对象永远振荡起来。
Stiffness可以理解成要恢复成未拉伸状态所需的时间,系统中有以下几个可选:
public static final float STIFFNESS_HIGH = 10000.0F;
public static final float STIFFNESS_MEDIUM = 1500.0F;
public static final float STIFFNESS_LOW = 200.0F;
public static final float STIFFNESS_VERY_LOW = 50.0F;
默认设置为STIFFNESS_MEDIUM,数值越大,恢复到之前状态的时间就越短。
刚性定义弹簧常数,用于测量弹簧的强度。当弹簧不在静止位置时,刚性弹簧对附着的物体施加更大的力。
setFinalPosition()方法指定最后静止时的位置。
4.创建自定义的动画属性
SpringAnimation和FlingAnimation的构造函数只能接收一个可动画属性参数,如ALPHA、ROTATION、SCALE等,如果要同时为多个属性生成动画,一个方法是创建多个对应类的实例,然后传入要改变的动画值,这种做法比较麻烦,可以创建一个新的属性,改属性封装了我们想改变的其他动画属性值。
FloatPropertyCompat<View> scale = new FloatPropertyCompat<View>("scale") { @Override public float getValue(View object) { //返回当前属性值 return object.getScaleX(); } @Override public void setValue(View object, float value) { //更新要修改的动画属性 object.setScaleX(value); object.setScaleX(value); } }; SpringAnimation stretchAnimation = new SpringAnimation(img, scale); stretchAnimation.setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);//传递一个有意义的值,以确保动画不会消耗太多的CPU性能 SpringForce force = new SpringForce(1); force.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY) .setStiffness(SpringForce.STIFFNESS_VERY_LOW); stretchAnimation.setSpring(force).setStartVelocity(100); stretchAnimation.start();
创建FloatPropertyCompat实例,在setValue()方法中更新要修改的动画属性,在getValue()方法中返回当前属性值,示例代码统一改变了SCALE_X和SCALE_Y属性,自定义属性创建好之后可以像其他动画属性一样使用它。
在创建使用自定义属性的动画时,最好也调用setMinimumVisibleChange()方法并传递一个有意义的值,以确保动画不会消耗太多的CPU性能。
5.动画监听
DynamicAnimation提供了两个动画监听器OnAnimationUpdateListener和OnAnimationEndListener,从名字也可以猜到前者监听动画值改变,后者监听动画结束状态。添加动画变化监听需要调用addUpdateListener()方法,重写onAnimationEnd()方法执行具体操作;如果移除动画监听,则需要调用removwUpdateListener()和removeEndListener()。
当动画结束时变换表情。
SpringAnimation springAnimation = new SpringAnimation(img, DynamicAnimation.X); SpringForce springForce = new SpringForce(); springForce.setFinalPosition(img.getX()); springForce.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY); springForce.setStiffness(SpringForce.STIFFNESS_LOW); springAnimation.setSpring(springForce); springAnimation.setStartVelocity(2000); springAnimation.start(); img.setImageResource(R.drawable.ic_sentiment_very_satisfied_black_56dp); springAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() { @Override public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity) { img.setImageResource(R.drawable.ic_sentiment_neutral_black_56dp); } });
6.一个使用动画的例子
使用三个圆圈,当拖动上面第一个圆圈的时候,下面两个圆圈跟着移动。
public class ChainedSpringActivity extends Activity { private float mDampingRatio = 1.0f; private float mStiffness = 50.0f; private View lead; private View follow1; private View follow2; private SeekBar dr; private SeekBar stiff; private TextView drTxt; private TextView nfTxt; private SpringAnimation animate1X; private SpringAnimation animate1Y; private SpringAnimation animate2X; private SpringAnimation animate2Y; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chained_spring); lead = findViewById(R.id.lead); follow1 = findViewById(R.id.follow1); follow2 = findViewById(R.id.follow2); dr = findViewById(R.id.damping_ratio); stiff = findViewById(R.id.stiffness); drTxt = findViewById(R.id.damping_ratio_txt); nfTxt = findViewById(R.id.stiffness_txt); animate1X = new SpringAnimation(follow1, DynamicAnimation.TRANSLATION_X, lead.getTranslationX());//DynamicAnimation.TRANSLATION_X表示动画的模式, lead.getTranslationX()表示动画停止的位置 animate1Y = new SpringAnimation(follow1, DynamicAnimation.TRANSLATION_Y, lead.getTranslationY()); animate2X = new SpringAnimation(follow2, DynamicAnimation.TRANSLATION_X, follow2.getTranslationX()); animate2Y = new SpringAnimation(follow2, DynamicAnimation.TRANSLATION_Y, follow2.getTranslationY()); animate1X.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() { @Override public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) { animate2X.animateToFinalPosition(value);//指定最后静止时的位置 } }); animate1Y.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() { @Override public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) { animate2Y.animateToFinalPosition(value);//指定最后静止时的位置 } }); ((View) lead.getParent()).setOnTouchListener(new View.OnTouchListener() { private float firstDownX = 0; private float firstDownY = 0; @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {//如果触摸的范围不在lead按钮上,则返回false if (motionEvent.getX() < lead.getX() || motionEvent.getX() > lead.getX() + lead.getWidth() || motionEvent.getY() < lead.getY() || motionEvent.getY() > lead.getY() + lead.getHeight() ) { return false; } //设置动画的设置弹性阻尼与生硬度 animate1X.getSpring().setDampingRatio(mDampingRatio).setStiffness(mStiffness); animate1Y.getSpring().setDampingRatio(mDampingRatio).setStiffness(mStiffness); animate2X.getSpring().setDampingRatio(mDampingRatio).setStiffness(mStiffness); animate2Y.getSpring().setDampingRatio(mDampingRatio).setStiffness(mStiffness); firstDownX = motionEvent.getX() - lead.getTranslationX(); firstDownY = motionEvent.getY() - lead.getTranslationY(); } else if (motionEvent.getActionMasked() == MotionEvent.ACTION_MOVE) { float deltaX = motionEvent.getX() - firstDownX; float deltaY = motionEvent.getY() - firstDownY; //移动lead的位置 lead.setTranslationX(deltaX); lead.setTranslationY(deltaY); //设置动画最后的位置 animate1X.animateToFinalPosition(deltaX); animate1Y.animateToFinalPosition(deltaY); } return true; } }); setupSeekBars(); } private void setupSeekBars() { dr.setMax(130); dr.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { if (i < 80) { mDampingRatio = i / 80.0f; } else if (i > 90) { mDampingRatio = (float) Math.exp((i - 90) / 10.0); } else { mDampingRatio = 1; } drTxt.setText(String.format("%.4f", (float) mDampingRatio)); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); stiff.setMax(110); stiff.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { float stiffness = (float) Math.exp(i / 10d); mStiffness = stiffness; nfTxt.setText(String.format("%.3f", (float) stiffness)); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); dr.setProgress(80); stiff.setProgress(60); } }
详细代码地址:https://github.com/ZhangMiao147/PhysicsBasedAnimationDemo
参考文章:
http://rkhcy.github.io/2017/07/07/PhysicsBasedAnimation%E5%AD%A6%E4%B9%A0/
http://blog.csdn.net/lyric_315/article/details/72758650