Material Component 动画基础—Spring Animation

Stella981
• 阅读 634

不管是在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,代码如下所示。

`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,效果如图所示。

Material Component 动画基础—Spring Animation

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效果:

Material Component 动画基础—Spring Animation

high_bounce-min

DAMPING_RATIO_MEDIUM_BOUNCY效果:

Material Component 动画基础—Spring Animation

medium_bounce-min

DAMPING_RATIO_LOW_BOUNCY效果:

Material Component 动画基础—Spring Animation

low_bounce-min

DAMPING_RATIO_NO_BOUNCY效果:

Material Component 动画基础—Spring Animation

no_bounce-min

相信大家通过GIF,就能很快明白其含义了。

刚度stiffness

刚度定义了用于衡量弹簧强度的弹簧常量。通过setStiffness()方法来设置刚度值,类似的,系统也定义了一些默认的刚度常量。

STIFFNESS_HIGH效果:

Material Component 动画基础—Spring Animation

high_stiffness-min

STIFFNESS_MEDIUM效果:

Material Component 动画基础—Spring Animation

medium_stiffness-min

STIFFNESS_LOW效果:

Material Component 动画基础—Spring Animation

low_stiffness-min

STIFFNESS_VERY_LOW效果:

Material Component 动画基础—Spring Animation

very_low_stiffness-min

阻尼

物理动画的另一个常用场景,则是创建拉动的阻尼效果,相比生硬的控制,通过阻尼设置拉动效果,动画会更加符合物理定律,让动画更加优雅。不过,设置阻尼动画,其实并不需要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) }

那么借助这样一个函数,就很方便的实现了变换效果,如图所示。

Material Component 动画基础—Spring Animation

zuni-min

其它

除了前面的示例外,这里还给大家提供了一些其它属性的使用示例。

旋转动画

设置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
}
`

效果如下所示。

Material Component 动画基础—Spring Animation

rotate-min

缩放

设置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 }

效果如图所示。

Material Component 动画基础—Spring Animation

scale-min

位移

设置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()     } }

效果如下所示。

Material Component 动画基础—Spring Animation

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源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
2星期前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
亚瑟 亚瑟
3年前
Flutter - 深入理解Flutter动画原理
基于Flutter1.5,从源码视角来深入剖析flutter动画原理,相关源码目录见文末附录一、概述动画效果对于系统的用户体验非常重要,好的动画能让用户感觉界面更加顺畅,提升用户体验。1.1动画类型Flutter动画大的分类来说主要分为两大类:补间动画:给定初值与终值,系统自动补齐中间帧的动画物理动画:遵循物理学定律
Stella981 Stella981
2年前
CocosCreator 教你玩转Animation动画(第十四篇)
前言:Animation动画在游戏中是必不可少的,各种人物的走跑跳飞,以及各种表情动作,反正做游戏Animation动画是必修课了。这一篇章可以学会制作和控制各种动画,主要从一下几个方面介绍:1.动画制作流程;2.使用Animation动画编辑器制作动画;3.代码控制动画;一、动画制作的流程
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Stella981 Stella981
2年前
Android 应用的动画实践
<h2id"menuIndex0"前言</h2<p尝试搜索了一下android动画的中文资料,很多都是一些枯燥的翻译api的一些文档,很少有系统讲解如何利用动画开发一个应用的资料,忽然,发现很多应用也不怎么注重动画在app的应用,想了想,自己尝试总结一下吧。因为,本人也不是什么动画制作师,没法把动画做得很绚丽,只好,利用内置的效果,进行简单加工
Python进阶者 Python进阶者
6个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这