Material Component 动画基础—Spring Animation
不管是在Android Material Design,还是Flutter中,Google都在尝试统一动画的行为和实现,在Google看来,动画基本都分为两种,即模拟动画和物理动画,本篇将介绍物理动画,这个概念在Android开发中,涉及的非常少,同时文档也最少,但却是实现很多优雅动画的基础,特别是MDC中封装的一些动画,在很多细节的处理上,都使用到了物理动画的概念。 弹性与阻尼 物理动画,顾名思义是基于物理学定律基础的动画效果,它实际上参考的就是弹簧的形变过程,即胡克定律,这种动画类型,通常被称为Spring Animation。官网上其实在很不起眼的小角落有一篇非常详细的文档,如下所示。 https://developer.android.com/guide/topics/graphics/spring-animation#add-support-library 对于Spring Animation来说,通常有两种常用的场景,即弹性和阻尼,弹性定义的是物体恢复到某个状态下的非线性过程,而阻尼,则定义的是拖动物体的非线性阻力。 关于Spring的设计定义,大家可以参考下这篇文章 https://zhuanlan.zhihu.com/p/127266926。 弹性 首先,我们来看下弹性的实现。首先,需要引入Google的Spring实现库,代码如下所示。 api'androidx.dynamicanimation:dynamicanimation:1.0.0' 要使用Spring Animation其实非常简单,定义SpringAnimation即可,甚至不用设置参数,下面就通过一个最简单的示例,来演示下如何使用Spring Animation,代码如下所示。 classMainActivity:AppCompatActivity(){varoffsetX:Float=0fvaroffsetY:Float=0f@SuppressLint("ClickableViewAccessibility")overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)test.setOnTouchListener{v,event->when(event.action){MotionEvent.ACTION_DOWN->{offsetX=event.rawXoffsetY=event.rawY}MotionEvent.ACTION_MOVE->{test.translationX=event.rawX-offsetXtest.translationY=event.rawY-offsetY}MotionEvent.ACTION_UP->{SpringAnimation(test,DynamicAnimation.TRANSLATION_Y).apply{spring=SpringForce().apply{//dampingRatio=DAMPING_RATIO_NO_BOUNCY//stiffness=SpringForce.STIFFNESS_VERY_LOW}animateToFinalPosition(0f)}SpringAnimation(test,DynamicAnimation.TRANSLATION_X).apply{spring=SpringForce().apply{//dampingRatio=DAMPING_RATIO_NO_BOUNCY//stiffness=SpringForce.STIFFNESS_VERY_LOW}animateToFinalPosition(0f)}}}true}}} 上面的代码定义了一个可以拖动并改变位置的View,当执行ACTION_UP的时候,就会执行定义的SpringAnimation,SpringAnimation需要几个参数,即TargetView、执行动画的属性、以及最终的属性值,而动画,则可以通过调用start(),或调用animateToFinalPosition()方法来启动,它们的区别就是是否设置了finalPosition,效果如图所示。 slide-min 很简单的几行代码就实现了弹性的效果。 哪些属性 基于Spring特性的动画可以更改屏幕上的View的实际属性,从而为View添加动画效果。系统中支持了下面这些属性。 ALPHA:表示视图的 Alpha 透明度。该值默认为 1(不透明),值为 0 则表示完全透明(不可见)。 TRANSLATION_X、TRANSLATION_Y 和 TRANSLATION_Z:这些属性用于控制视图所在的位置,值为视图的布局容器所设置的左侧坐标、顶部坐标和高度的增量。 TRANSLATION_X 表示左侧坐标。 TRANSLATION_Y 表示顶部坐标。 TRANSLATION_Z 表示视图相对于其高度的深度。 ROTATION、ROTATION_X 和 ROTATION_Y:这些属性用于控制视图围绕轴心点进行的 2D(rotation属性)和 3D 旋转。 SCROLL_X 和 SCROLL_Y:这些属性分别表示视图距离源左侧和顶部边缘的滚动偏移量(以像素为单位)。它还以页面滚动的距离来表示位置。 SCALE_X 和 SCALE_Y:这些属性用于控制视图围绕其轴心点进行的 2D 缩放。X、Y 和 Z:这些是基本的实用属性,用于描述视图在容器中的最终位置。 X 是左侧值与 TRANSLATION_X 的和。 Y 是顶部值与 TRANSLATION_Y 的和。 Z 是高度值与 TRANSLATION_Z 的和。 实际上与属性动画的默认属性值基本是一致的,其本质,也是借助了属性动画来实现Spring效果。 监听 与属性动画一样,Spring Animation同样可以监听其动画的生命周期,系统提供了OnAnimationUpdateListener和OnAnimationEndListener,来监听动画。 通过addUpdateListener来设置,代码如下所示。 anim.addUpdateListener{_,value,_->anim.animateToFinalPosition(value)} 与之对应的,系统提供了removeUpdateListener() 和 removeEndListener()方法来移除监听。 SpringForce 对应一个弹性系统来说,SpringForce是描述该弹性系统的各种参数的封装。 SpringForce:定义动画具有的弹簧特征。其中有四个关键的参数: finalPosition:静止位置。 stiffness:即弹簧常数,物体的弹性系数。 dampingRatio:阻尼比。描述系统扰动后的振荡衰减过程。 velocity:运动的初速度。 在这四个参数中,finalPosition用于描述动画最终停止的位置,而velocity则是描述物体运动的初速度,默认情况下,velocity为0,系统提供了setStartVelocity()方法来改变这个初速度,在大部分情况下,我们都不用修改。 剩下两个属性stiffness和dampingRatio,则是Spring Animation的核心配置参数。 阻尼比dampingRatio 阻尼比用于描述弹簧振动逐渐衰减的状况。通过使用阻尼比,可以定义振动从一次弹跳到下一次弹跳所衰减的速度有多快。 当阻尼比大于 1 时,会出现过阻尼现象。它会使对象快速地返回到静止位置。 当阻尼比等于 1 时,会出现临界阻尼现象。这会使对象在最短时间内返回到静止位置。 当阻尼比小于 1 时,会出现欠阻尼现象。这会使对象多次经过并越过静止位置,然后逐渐到达静止位置。 当阻尼比等于零时,便会出现无阻尼现象。这会使对象永远振动下去。 一般来说,首先需要调用getSpring()方法来获取当前的参数,再通过调用setDampingRatio()方法设置要增加到弹簧上的阻尼比。 系统同时也定义了一些常用的dampingRatio。 DAMPING_RATIO_HIGH_BOUNCY效果: high_bounce-min DAMPING_RATIO_MEDIUM_BOUNCY效果: medium_bounce-min DAMPING_RATIO_LOW_BOUNCY效果: low_bounce-min DAMPING_RATIO_NO_BOUNCY效果: no_bounce-min 相信大家通过GIF,就能很快明白其含义了。 刚度stiffness 刚度定义了用于衡量弹簧强度的弹簧常量。通过setStiffness()方法来设置刚度值,类似的,系统也定义了一些默认的刚度常量。 STIFFNESS_HIGH效果: high_stiffness-min STIFFNESS_MEDIUM效果: medium_stiffness-min STIFFNESS_LOW效果: low_stiffness-min STIFFNESS_VERY_LOW效果: very_low_stiffness-min 阻尼 物理动画的另一个常用场景,则是创建拉动的阻尼效果,相比生硬的控制,通过阻尼设置拉动效果,动画会更加符合物理定律,让动画更加优雅。不过,设置阻尼动画,其实并不需要Google的Spring Animation,我们通过一个函数,即可完成阻尼效果的实现,其实,所谓的阻尼,即在拉动过程中,将线性的拉动距离,通过一个函数变换,转换为非线性的递减函数,递减函数的斜率,即为阻尼的力度,这样的函数有很多,这里介绍其中一种。 阻尼效果 privatefundoDamping(value:Float):Float{returnif(value<0)-sqrt((100f*abs(value)).toDouble()).toFloat()elsesqrt((100f*value).toDouble()).toFloat()} 通过这样一个变换,就可以实现阻尼效果。 例如我们将第一个场景中的拉动效果来增加阻尼效果,只需要在Move的过程中,不断改变偏移量即可,代码如下所示。 MotionEvent.ACTION_MOVE->{test.translationX=doDamping(event.rawX-offsetX)test.translationY=doDamping(event.rawY-offsetY)} 那么借助这样一个函数,就很方便的实现了变换效果,如图所示。 zuni-min 其它 除了前面的示例外,这里还给大家提供了一些其它属性的使用示例。 旋转动画 设置ROTATION属性,代码如下所示。 test.setOnTouchListener{view,event->valcenterX=view.width/2.0valcenterY=view.height/2.0valx=event.xvaly=event.yfunupdateRotation(){currentRotation=view.rotation+Math.toDegrees(atan2(x-centerX,centerY-y)).toFloat()}when(event.actionMasked){MotionEvent.ACTION_DOWN->updateRotation()MotionEvent.ACTION_MOVE->{previousRotation=currentRotationupdateRotation()valangle=currentRotation-previousRotationview.rotation+=angle}MotionEvent.ACTION_UP->SpringAnimation(test,DynamicAnimation.ROTATION).animateToFinalPosition(0f)}true} 效果如下所示。 rotate-min 缩放 设置SCALE_X和SCALE_Y属性,代码如下所示。 scaleGestureDetector=ScaleGestureDetector(this,object:ScaleGestureDetector.SimpleOnScaleGestureListener(){overridefunonScale(detector:ScaleGestureDetector):Boolean{scaleFactor*=detector.scaleFactortest.scaleX*=scaleFactortest.scaleY*=scaleFactorreturntrue}})test.setOnTouchListener{_,event->if(event.action==MotionEvent.ACTION_UP){SpringAnimation(test,DynamicAnimation.SCALE_X).animateToFinalPosition(1f)SpringAnimation(test,DynamicAnimation.SCALE_Y).animateToFinalPosition(1f)}else{scaleGestureDetector.onTouchEvent(event)}true} 效果如图所示。 scale-min 位移 设置TRANSLATION_Y来实现View出现的动画效果,代码如下所示。 overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)test.translationY=1600fSpringAnimation(test,SpringAnimation.TRANSLATION_Y,0f).apply{spring.stiffness=SpringForce.STIFFNESS_VERY_LOWspring.dampingRatio=SpringForce.DAMPING_RATIO_LOW_BOUNCYsetStartVelocity(-2000f)start()}} 效果如下所示。 up-min KTX 在KTX中,Google还基于Spring Animation,提供了一些拓展函数,来进一步简化Spring的使用,地址如下所示。 implementation"androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03" 虽然还没Release,虽然也并没有什么用,但为了文章的完整性,还是提一下吧。 本文分享自微信公众号 - Android群英传(android_heroes)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。