时间:2021-05-20
SwiftUI 中的动画
在写动画之前呢先简单回顾一下 SwiftUI 中动画的几个要点:
反弹动画
反弹动画属于“起始点和终止点相等”的动画,所以不能够通过 SwiftUI 中内建的动画来实现(因为这个 view 从结果来看没有发生变化)
我们先来构建反弹动画修饰器的框架如下:
struct Bounce: AnimatableModifier { var animCount: CGFloat = 0 var amplitude: CGFloat = 10 // 振幅 var animatableData: CGFloat { get { animCount } set { animCount = newValue } } func body(content: Content) -> some View { // change view to animate }}下面一步一步来
实现动画曲线
实现 body 方法,好让 times 每次增加时 view 能以反弹的方式进行动画。一次反弹就是 view 向上弹起又落下,下面是动画曲线大致的样子:
三角函数是 y = -abs(sin x) ,所以 body 方法这样实现:
struct Bounce: AnimatableModifier { ... func body(content: Content) -> some View { let offset: CGFloat = -abs(sin(animCount * .pi * 2) * amplitude) return content.offset(y: offset) }}测试代码:
struct BouncingView: View { @State var taps = 0 var body: some View { Button(action: { withAnimation(Animation.easeInOut(duration: 1)) { taps += 1 } }, label: { RoundedRectangle(cornerRadius: 15) .modifier(Bounce(animCount: CGFloat(taps))) }) .frame(width: 100, height: 100) }}点击按钮,就能弹两次了~~
这个 @State var taps
其实并没有什么实际的意义,只是为了触发动画。
因为 SwiftUI 里只有 View 的状态发生变化才会触发动画,而无法通过某个事件来触发;而我们的动画是一个初始状态和结束状态相等的情况,并没有状态的变化,所以这里强行把点击的次数作为 View 状态的变化来触发动画。
我找了好久有没有更优雅的方式来解决这个问题,然而并没有找到 = =b
View 扩展
暴露给外面的 animCount 应该是一个 Int 才对,那么就增加一个 animValue 来代替它
struct Bounce: AnimatableModifier { let animCount: Int var animValue: CGFloat var amplitude: CGFloat = 10 // 振幅 init(animCount: Int) { self.animCount = animCount self.animValue = CGFloat(animCount) } var animatableData: CGFloat { get { animValue } set { animValue = newValue } } func body(content: Content) -> some View { let offset: CGFloat = -abs(sin(animValue * .pi * 2) * amplitude) return content.offset(y: offset) }}因为 animValue 被隐藏起来了,所以需要初始化方法
为了方便使用,再添加一个 View 的扩展方法:
extension View { func bounce(animCount: Int) -> some View { self.modifier(Bounce(animCount: animCount)) }}增加弹性
现在虽然能弹了,但是相对还比较生硬,就想在 View “落地“后再给它增加振幅逐步衰减的几次反弹;一开始尝试了简单的减半反弹,实验证明观感更差,看起来有点难受。
我们实际生活中的反弹的振幅变化应该是符合 阻尼正弦波 的,参考链接里的公式,修改如下:
struct Bounce: AnimatableModifier { let animCount: Int var animValue: CGFloat var amplitude: CGFloat // 振幅 var bouncingTimes: Int init(animCount: Int, amplitude: CGFloat = 10, bouncingTimes: Int = 3) { self.animCount = animCount self.animValue = CGFloat(animCount) self.amplitude = amplitude self.bouncingTimes = bouncingTimes } var animatableData: CGFloat { get { animValue } set { animValue = newValue } } func body(content: Content) -> some View { let t = animValue - CGFloat(animCount) let offset: CGFloat = -abs(pow(CGFloat(M_E), -t) * sin(t * .pi * CGFloat(bouncingTimes)) * amplitude) return content.offset(y: offset) }}extension View { func bounce(animCount: Int, amplitude: CGFloat = 10, bouncingTimes: Int = 3) -> some View { self.modifier(Bounce(animCount: animCount, amplitude: amplitude, bouncingTimes: bouncingTimes)) }}这里我们还增加了 bouncingTimes 作为弹跳次数的参数,振幅也作为参数开放给用户;
由于阻尼正弦波是逐步衰减的,为了每次点击的弹跳都一样,所以得用 animValue - CGFloat(animCount) 作为阻尼正弦波函数的参数。
测试代码修改如下:
struct BouncingView: View { @State var taps = 0 var body: some View { Button(action: { withAnimation(Animation.linear(duration: 1)) { taps += 1 } }, label: { RoundedRectangle(cornerRadius: 15) .bounce(animCount: taps) }) .frame(width: 100, height: 100) }}实际效果如下:
到此这篇关于SwiftUI 中创建反弹动画的实现的文章就介绍到这了,更多相关SwiftUI 反弹动画内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
scratch3.0中可以创建小球角色并添加脚本,让它不断运动,遇到边缘就反弹变色,形成动画,该怎么制作这个效果呢?下面我们就来看看详细的教程。软件名称:scr
CSS3@keyframes简单动画实现定义:通过@keyframes规则,能够创建动画。创建动画的原理是,将一套CSS样式逐渐变化为另一套样式。在动画过程中,
在用scratch创建运动动画时,给角色设置碰到边缘就反弹后,发现角色倒过来了,非常的讨厌,那怎么解决这个问题呢?软件名称:Scratch2OfflineEdi
微信小程序实现animation动画,具体内容如下1.创建动画实例wx.createAnimation(OBJECT)创建一个动画实例animation。调用实
ae中想要制作一个文字动画,实现的效果是文字从显示区底部左边滚动到右边,反弹到显示中心,再移动到左边;其它文字也跟着一样的动作,最后在显示区排列而成一行。该怎么