Animatable

Latest release

contact: @lexkraev Telegram Group

又一个纯 SwiftUI 实现的按钮、骨架和其他视图的动画修饰器。

卡片骨架的演示示例

🔍 完整的演示视频可以在这里找到。

👨🏻‍💻 欢迎订阅 Telegram 频道 SwiftUI dev

要求

安装

Swift Package Manager

要使用 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 提供了不同的自定义动画类型。

👇🏻 点击其名称以查看描述和使用示例。

实时评论效果

live comments

使用 .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)
    )
}
爆炸效果

explosion

使用 .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)
  )
}
抖动效果

tweak

使用 .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)
  )
}
缩放效果

scaling

使用 .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)
  )
}
旋转效果

scaling

使用 .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)
  )
}
烟花效果

fireworks

使用 .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)
  )
}

👇🏻 您可以轻松地将它们组合在一起以结合动画。

组合动画

combined

使用 .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)
  )
}

👇🏻 用于骨架和其他视图的动画。

闪光

combined

使用 .shimmerable(configuration:) 的序列,其中 configuration 是闪光的设置,或者使用 .shimmerable() 来使用默认设置

Rectangle()
  .fill(.pink.opacity(0.8))
  .frame(height: 40)
  .frame(maxWidth: .infinity)
  .shimmerable()
  .cornerRadius(12)
闪烁

combined

使用 .blinking(configuration:) 的序列,其中 configuration 是闪烁动画的设置,或者使用 .blinking() 来使用默认设置

Rectangle()
  .fill(.pink.opacity(0.8))
  .frame(height: 40)
  .frame(maxWidth: .infinity)
  .blinking()
  .cornerRadius(12)
组合闪烁和闪光

combined

使用 .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(...) 序列会导致这种行为

problem1

要获得预期的行为,我们应该更改链中的顺序

@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)
    )
}

结果

erro1

关于视图组

您不仅可以对单个视图使用 .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)
  )
}

结果

example1

关于标准的 SUI 修饰符

小心使用标准的 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!
}

结果

problem2

我建议您在 .background 中使用它以获得预期的行为

.background ( // <== Look here
  Rectangle()
      .fill(.blue.opacity(0.8))
      .cornerRadius(12) // <== Look here
)

交流

许可证

Animatable 包是在 MIT 许可证下发布的。