不论是在Android Material Design,仍是Flutter中,Google都在尝试统一动画的行为和实现,在Google看来,动画基本都分为两种,即模拟动画和物理动画,本篇将介绍物理动画,这个概念在Android开发中,涉及的很是少,同时文档也最少,但倒是实现不少优雅动画的基础,特别是MDC中封装的一些动画,在不少细节的处理上,都使用到了物理动画的概念。android
弹性与阻尼
物理动画,顾名思义是基于物理学定律基础的动画效果,它实际上参考的就是弹簧的形变过程,即胡克定律,这种动画类型,一般被称为Spring Animation。官网上其实在很不起眼的小角落有一篇很是详细的文档,以下所示。web
https://developer.android.com/guide/topics/graphics/spring-animation#add-support-libraryspring
对于Spring Animation来讲,一般有两种经常使用的场景,即弹性和阻尼,弹性定义的是物体恢复到某个状态下的非线性过程,而阻尼,则定义的是拖动物体的非线性阻力。api
关于Spring的设计定义,你们能够参考下这篇文章 https://zhuanlan.zhihu.com/p/127266926。微信
弹性
首先,咱们来看下弹性的实现。首先,须要引入Google的Spring实现库,代码以下所示。app
api 'androidx.dynamicanimation:dynamicanimation:1.0.0'
要使用Spring Animation其实很是简单,定义SpringAnimation便可,甚至不用设置参数,下面就经过一个最简单的示例,来演示下如何使用Spring Animation,代码以下所示。编辑器
class MainActivity : AppCompatActivity() {
var offsetX: Float = 0f
var offsetY: Float = 0f
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
test.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
offsetX = event.rawX
offsetY = event.rawY
}
MotionEvent.ACTION_MOVE -> {
test.translationX = event.rawX - offsetX
test.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,效果如图所示。ide

很简单的几行代码就实现了弹性的效果。函数
哪些属性
基于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效果:

DAMPING_RATIO_MEDIUM_BOUNCY效果:

DAMPING_RATIO_LOW_BOUNCY效果:

DAMPING_RATIO_NO_BOUNCY效果:

相信你们经过GIF,就能很快明白其含义了。
刚度stiffness
刚度定义了用于衡量弹簧强度的弹簧常量。经过setStiffness()方法来设置刚度值,相似的,系统也定义了一些默认的刚度常量。
STIFFNESS_HIGH效果:

STIFFNESS_MEDIUM效果:

STIFFNESS_LOW效果:

STIFFNESS_VERY_LOW效果:

阻尼
物理动画的另外一个经常使用场景,则是建立拉动的阻尼效果,相比生硬的控制,经过阻尼设置拉动效果,动画会更加符合物理定律,让动画更加优雅。不过,设置阻尼动画,其实并不须要Google的Spring Animation,咱们经过一个函数,便可完成阻尼效果的实现,其实,所谓的阻尼,即在拉动过程当中,将线性的拉动距离,经过一个函数变换,转换为非线性的递减函数,递减函数的斜率,即为阻尼的力度,这样的函数有不少,这里介绍其中一种。
阻尼效果
private fun doDamping(value: Float): Float {
return if (value < 0)
-sqrt((100f * abs(value)).toDouble()).toFloat()
else
sqrt((100f * value).toDouble()).toFloat()
}
经过这样一个变换,就能够实现阻尼效果。
例如咱们将第一个场景中的拉动效果来增长阻尼效果,只须要在Move的过程当中,不断改变偏移量便可,代码以下所示。
MotionEvent.ACTION_MOVE -> {
test.translationX = doDamping(event.rawX - offsetX)
test.translationY = doDamping(event.rawY - offsetY)
}
那么借助这样一个函数,就很方便的实现了变换效果,如图所示。

其它
除了前面的示例外,这里还给你们提供了一些其它属性的使用示例。
旋转动画
设置ROTATION属性,代码以下所示。
test.setOnTouchListener { view, event ->
val centerX = view.width / 2.0
val centerY = view.height / 2.0
val x = event.x
val y = event.y
fun updateRotation() {
currentRotation = view.rotation + Math.toDegrees(atan2(x - centerX, centerY - y)).toFloat()
}
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> updateRotation()
MotionEvent.ACTION_MOVE -> {
previousRotation = currentRotation
updateRotation()
val angle = currentRotation - previousRotation
view.rotation += angle
}
MotionEvent.ACTION_UP -> SpringAnimation(
test,
DynamicAnimation.ROTATION
).animateToFinalPosition(0f)
}
true
}
效果以下所示。

缩放
设置SCALE_X和SCALE_Y属性,代码以下所示。
scaleGestureDetector = ScaleGestureDetector(this,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
scaleFactor *= detector.scaleFactor
test.scaleX *= scaleFactor
test.scaleY *= scaleFactor
return true
}
})
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
}
效果如图所示。

位移
设置TRANSLATION_Y来实现View出现的动画效果,代码以下所示。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
test.translationY = 1600f
SpringAnimation(test, SpringAnimation.TRANSLATION_Y, 0f).apply {
spring.stiffness = SpringForce.STIFFNESS_VERY_LOW
spring.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
setStartVelocity(-2000f)
start()
}
}
效果以下所示。

KTX
在KTX中,Google还基于Spring Animation,提供了一些拓展函数,来进一步简化Spring的使用,地址以下所示。
implementation "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03"
虽然还没Release,虽然也并无什么用,但为了文章的完整性,仍是提一下吧。
本文分享自微信公众号 - Android群英传(android_heroes)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。