又一个纯 SwiftUI 实现的按钮、骨架和其他视图的动画修饰器。
🔍 完整的演示视频可以在这里找到。
👨🏻💻 欢迎订阅 Telegram 频道 SwiftUI dev。
要使用 SwiftPM 将 Animatable
集成到您的项目中,请将以下内容添加到您的 Package.swift
文件中
dependencies: [
.package(url: "https://github.com/c-villain/Animatable", from: "0.1.0"),
],
或者通过 XcodeGen 插入到您的 project.yml
文件中
name: YourProjectName
options:
deploymentTarget:
iOS: 13.0
packages:
Animatable:
url: https://github.com/c-villain/Animatable
from: 0.1.0
targets:
YourTarget:
type: application
...
dependencies:
- package: Animatable
所有示例都可以在包内的演示项目中找到。
Animatable
提供了不同的自定义动画类型。
👇🏻 点击其名称以查看描述和使用示例。
使用 .animate(.liveComments(stamps:),animate:)
,其中 stamps
是动画活动中的打印数量,animate
是启动动画的标志。
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: animate ? "heart.fill" : "heart")
.resizable()
.scaledToFit()
.animate(.liveComments(stamps: 4),
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.white)
Text("Like")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background(
Rectangle()
.fill(.pink.opacity(0.8))
.cornerRadius(12)
)
}
使用 .animate(.explosive(color:),animate:)
,其中 color
是动画活动中的爆炸颜色,animate
是启动动画的标志。
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: animate ? "power" : "poweroff")
.resizable()
.scaledToFit()
.animate(.explosive(color: .white),
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.white)
Text(animate ? "On" : "Off")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background(
Rectangle()
.fill(.gray.opacity(0.8))
.cornerRadius(12)
)
}
使用 .animate(.tweaking(amount:,shakesPerUnit:),animate:)
,其中 amount
是抖动偏移量,shakesPerUnit
是抖动次数,animate
是启动动画的标志。
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: animate ? "hand.thumbsup.fill" : "hand.thumbsup")
.resizable()
.scaledToFit()
.animate(.tweaking(),
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.white)
Text("Like")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background(
Rectangle()
.fill(.blue.opacity(0.8))
.cornerRadius(12)
)
}
使用 .animate(.scaling(scaling:),animate:)
,其中 scaling
是缩放比例,animate
是启动动画的标志。
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: animate ? "plus.app.fill" : "plus.app")
.resizable()
.scaledToFit()
.animate(.scaling(),
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.white)
Text("Add")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background(
Rectangle()
.fill(.yellow.opacity(0.8))
.cornerRadius(12)
)
}
使用 .animate(.rotating,animate:)
,其中 animate
是启动动画的标志。
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: animate ? "arrow.triangle.2.circlepath.circle.fill" : "arrow.triangle.2.circlepath.circle")
.resizable()
.scaledToFit()
.animate(.rotating,
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.white)
Text("Sync")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background(
Rectangle()
.fill(.blue.opacity(0.8))
.cornerRadius(12)
)
}
使用 .animate(.fireworks(color:),animate:)
,其中 color
是动画的颜色,animate
是启动动画的标志。
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: animate ? "sun.max.fill" : "sun.max")
.resizable()
.scaledToFit()
.animate(.fireworks(color: .white),
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.white)
Text("Weather")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background(
Rectangle()
.fill(.blue.opacity(0.8))
.cornerRadius(12)
)
}
👇🏻 您可以轻松地将它们组合在一起以结合动画。
使用 .animate(type:,animate:)
的序列来获得多种动画效果。
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: animate ? "sun.max.fill" : "sun.max")
.resizable()
.scaledToFit()
.animate(.rotating,
animate: animate)
.animate(.explosive(color: .red, factor: 2.0),
animate: animate)
.animate(.explosive(color: .blue, factor: 1.4),
animate: animate)
.animate(.fireworks(color: .yellow, factor: 3.5),
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.red)
Text("Combined")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background(
Rectangle()
.fill(.blue.opacity(0.6))
.cornerRadius(12)
)
}
👇🏻 用于骨架和其他视图的动画。
使用 .shimmerable(configuration:)
的序列,其中 configuration
是闪光的设置,或者使用 .shimmerable()
来使用默认设置
Rectangle()
.fill(.pink.opacity(0.8))
.frame(height: 40)
.frame(maxWidth: .infinity)
.shimmerable()
.cornerRadius(12)
使用 .blinking(configuration:)
的序列,其中 configuration
是闪烁动画的设置,或者使用 .blinking()
来使用默认设置
Rectangle()
.fill(.pink.opacity(0.8))
.frame(height: 40)
.frame(maxWidth: .infinity)
.blinking()
.cornerRadius(12)
使用 .blinking()
和 .shimmerable()
的序列来获得多种动画效果。
Rectangle()
.fill(.pink.opacity(0.8))
.frame(height: 40)
.frame(maxWidth: .infinity)
.blinking()
.shimmerable()
.cornerRadius(12)
.animate(type:,animate:)
序列中的顺序非常重要!
感受一下下一个示例中的差异
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: multiple ? "sun.max.fill" : "sun.max")
.resizable()
.scaledToFit()
.animate(.liveComments(stamps: 4),
animate: animate)
.animate(.rotating,
animate: animate)
.animate(.explosive(color: .red, factor: 2.0),
animate: animate)
.animate(.explosive(color: .blue, factor: 1.4),
animate: animate)
.animate(.fireworks(color: .yellow, factor: 3.0),
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.red)
Text("Weather")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background(
Rectangle()
.fill(.blue.opacity(0.6))
.cornerRadius(12)
)
}
使用这种 .animate(...)
序列会导致这种行为
要获得预期的行为,我们应该更改链中的顺序
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: multiple ? "sun.max.fill" : "sun.max")
.resizable()
.scaledToFit()
.animate(.rotating, // <== Look here!
animate: animate)
.animate(.liveComments(stamps: 4), // <== Look here!
animate: animate)
.animate(.explosive(color: .red, factor: 2.0),
animate: animate)
.animate(.explosive(color: .blue, factor: 1.4),
animate: animate)
.animate(.fireworks(color: .yellow, factor: 3.0),
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.red)
Text("Weather")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background(
Rectangle()
.fill(.blue.opacity(0.6))
.cornerRadius(12)
)
}
结果
您不仅可以对单个视图使用 .animate(...)
,还可以对视图组使用
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: animate ? "heart.fill" : "heart")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.red)
Text("Like")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.red)
}
.animate(.liveComments(stamps: 4), // <== Look here!
animate: animate)
.padding(12)
.background(
Rectangle()
.fill(.blue.opacity(0.8))
.cornerRadius(12)
)
}
结果
小心使用标准的 SUI 修饰符。 这可能会导致不同的副作用。
例如,cornerRadius
会裁剪修改后的视图。
@State var animate: Bool = false
...
Button {
animate.toggle()
} label: {
HStack(spacing: 8) {
Image(systemName: liveComments ? "heart.fill" : "heart")
.resizable()
.scaledToFit()
.animate(.liveComments(stamps: 4),
animate: animate)
.frame(width: 24, height: 24)
.foregroundColor(.red)
Text("Like")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.white)
}
.padding(12)
.background (
Color.blue.opacity(0.8)
)
.cornerRadius(12) // <== Look here!
}
结果
我建议您在 .background
中使用它以获得预期的行为
.background ( // <== Look here
Rectangle()
.fill(.blue.opacity(0.8))
.cornerRadius(12) // <== Look here
)
Animatable 包是在 MIT 许可证下发布的。