SwiftUI动画数据类型AnimatableData和AnimatablePair
SwiftUI动画数据类型AnimatableData和AnimatablePair

SwiftUI动画数据类型AnimatableData和AnimatablePair

在 SwiftUI 中,AnimatableData 和 AnimatablePair 是动画的核心概念,特别是当需要对复杂的形状或自定义组件实现动画时。以下是它们的详细解释和 AnimatableArc 的实现说明:

1、AnimatableData

AnimatableData 是 Shape 和其他可动画视图的重要协议属性,用于描述该视图中可以参与动画的属性。

特点

每个遵循 Shape 协议的自定义形状都需要实现 animatableData。

它通常存储形状的可动画参数,例如角度、位置、比例等。

默认实现

对于简单数据类型(如 CGFloat 或 Double),animatableData 通常是这些类型本身。

对于多值情况(如两个角度),可以使用 AnimatablePair。

2、AnimatablePair

AnimatablePair 是一个 SwiftUI 提供的类型,用于同时存储和动画两个不同的值。

特点

用于将多个动画数据值组合成一个。

可以嵌套,支持更复杂的动画场景(如三值组合 AnimatablePair<AnimatablePair<T, U>, V>)。

主要属性

.first:第一个值。

.second:第二个值。

用法场景

当一个形状需要两个或多个动画参数时(如起始角度和结束角度),使用 AnimatablePair 将它们组合起来。

AnimatableArc 示例

下面是一个完整的 AnimatableArc 示例,展示了如何使用 AnimatableData 和 AnimatablePair 实现动画。

实现

import SwiftUI

struct AnimatableArc: Shape {
    // 起始角度和结束角度
    var startAngle: Angle
    var endAngle: Angle
    
    // 动画数据,使用 AnimatablePair 将两个角度组合
    var animatableData: AnimatablePair<Double, Double> {
        get { AnimatablePair(startAngle.radians, endAngle.radians) }
        set {
            startAngle = .radians(newValue.first) // 解包第一个值
            endAngle = .radians(newValue.second) // 解包第二个值
        }
    }
    
    // 绘制路径
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = min(rect.width, rect.height) / 2
        
        path.addArc(center: center,
                    radius: radius,
                    startAngle: startAngle,
                    endAngle: endAngle,
                    clockwise: false)
        return path
    }
}

逻辑代码解析

1、基本定义

struct AnimatableArc: Shape {
    var startAngle: Angle
    var endAngle: Angle
}

AnimatableArc:这是一个结构体,遵循了 SwiftUI 的 Shape 协议。Shape 是用于绘制形状(如矩形、圆形、自定义路径等)的协议。

startAngle 和 endAngle:定义了弧形的起始角度和结束角度。它们的类型是 Angle,SwiftUI 提供的一个类型,用于更直观地表示角度(可以使用 .degrees 或 .radians 创建)。

2、动画数据支持

var animatableData: AnimatablePair<Double, Double> {
    get { AnimatablePair(startAngle.radians, endAngle.radians) }
    set {
        startAngle = .radians(newValue.first)
        endAngle = .radians(newValue.second)
    }
}

这是实现动画支持的核心代码。

animatableData

Shape 协议要求实现的属性。

它定义了形状支持动画的属性(例如,弧形的角度变化)。

类型是 AnimatablePair<Double, Double>,表示一个由两个动画值组成的组合。这里将起始角度和结束角度的弧度值组合起来。

如何工作

1)get:将 startAngle 和 endAngle 转换为弧度值,并包装成一个 AnimatablePair。

2)set:在动画过程中,SwiftUI 会逐步更新 newValue,将它解包回 startAngle 和 endAngle。

通过这种方式,当弧形的角度发生变化时,AnimatableArc 会通知 SwiftUI 使用平滑动画来绘制新的弧形。

3、路径绘制

func path(in rect: CGRect) -> Path {
    var path = Path()
    let center = CGPoint(x: rect.midX, y: rect.midY)
    let radius = min(rect.width, rect.height) / 2

    path.addArc(center: center,
                radius: radius,
                startAngle: startAngle,
                endAngle: endAngle,
                clockwise: false)
    return path
}

这是 Shape 协议中绘制形状的核心方法。

path(in rect:)

接收一个矩形区域(CGRect),在这个区域内绘制形状。

返回一个 Path,定义了弧形的绘制逻辑。

代码解析

1、center 和 radius:定义弧形的圆心和半径,圆心位于矩形的中心,半径是矩形宽高较小值的一半。

2、path.addArc:使用 addArc 方法在路径中添加一个弧形。

startAngle:弧形的起始角度。

endAngle:弧形的结束角度。

clockwise:是否顺时针绘制,这里设置为 false,表示逆时针绘制。

使用 AnimatableArc 的动画示例

struct ContentView: View {
    @State private var progress: Double = 0.0
    
    var body: some View {
        VStack {
            AnimatableArc(startAngle: .degrees(0), endAngle: .degrees(360 * progress))
                .stroke(Color.blue, lineWidth: 5)
                .frame(width: 200, height: 200)
                .onTapGesture {
                    withAnimation(.linear(duration: 1)) {
                        progress = progress == 1.0 ? 0.0 : 1.0
                    }
                }
            
            Slider(value: $progress)
                .padding()
        }
    }
}

视图代码解析

1、动画驱动

在 onTapGesture 中,通过 withAnimation 设置动画,改变 progress 值。

progress 变化会触发 endAngle 的更新,完成圆弧的动态绘制。

2、灵活性

可以轻松扩展为支持多参数的动画。例如,引入弧线颜色或宽度的动画时,可以嵌套更多 AnimatablePair。

AnimatableData 的作用

AnimatableData 的主要作用是将形状中需要动画的数据暴露给 SwiftUI 动画系统,确保数据在动画期间以平滑的过渡更新。

常见实现

单值动画:CGFloat 或 Double

var animatableData: CGFloat

多值动画:AnimatablePair

var animatableData: AnimatablePair<Double, Double>

嵌套多值动画

var animatableData: AnimatablePair<AnimatablePair<Double, Double>, Double>

总结

AnimatableData 是一个桥梁,用于 SwiftUI 的动画系统和形状内部的数据更新。

AnimatablePair 是一种数据类型,支持组合多个动画属性。

通过自定义 Shape 并实现 AnimatableData,可以轻松实现复杂的动画效果。

扩展知识

animatableData的实现

这段代码是实现 Shape 协议的核心部分,用于支持动画的动态数据。逐行分析其含义:

var animatableData: AnimatablePair<Double, Double> {
    get { AnimatablePair(startAngle.radians, endAngle.radians) }
    set {
        startAngle = .radians(newValue.first)
        endAngle = .radians(newValue.second)
    }
}

animatableData 的含义

animatableData 是 Shape 协议中用于定义“动画数据”的属性。

SwiftUI 动画会自动监控 animatableData,并在动画期间逐步更新其值,从而实现动画效果。

AnimatablePair 和 Angle 的用法

1、AnimatablePair<Double, Double>

AnimatablePair 是 SwiftUI 提供的一个类型,用于存储和管理两个动画值。

这里它存储两个 Double 类型的值:startAngle 和 endAngle 的弧度值。

动画时,SwiftUI 会逐步改变 AnimatablePair 中的 first 和 second 值。

2、get 部分

通过 AnimatablePair 将 startAngle 和 endAngle 转换为弧度值并组合:

AnimatablePair(startAngle.radians, endAngle.radians)

startAngle.radians 和 endAngle.radians 是 Angle 的方法,将角度值转换为弧度表示的 Double 类型。

3、set 部分

动画时,SwiftUI 会更新 newValue,它是一个包含动画目标值的 AnimatablePair。

newValue.first 和 newValue.second 分别表示起始角度和结束角度的目标弧度值。

这些值被重新解包并赋值给 startAngle 和 endAngle:

startAngle = .radians(newValue.first)
endAngle = .radians(newValue.second)

核心内容AnimatablePair主要的作用是封装两个可以动画化的值,以便通过get 方法会返回一个 AnimatablePair<Double, Double>,表示起点和终点的弧度值。

一个变量用 AnimatableData,多个变量用 AnimatablePair 吗?

这是大致的规则,但具体实现会因需求而异:

单个动画变量

如果只有一个动画变量(如某个属性是单一值),可以直接使用 AnimatableData:

struct AnimatableCircle: Shape {
    var radius: CGFloat
    
    var animatableData: CGFloat { // 单一值,直接返回
        get { radius }
        set { radius = newValue }
    }
    
    func path(in rect: CGRect) -> Path {
        Path { path in
            path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
                        radius: radius,
                        startAngle: .degrees(0),
                        endAngle: .degrees(360),
                        clockwise: false)
        }
    }
}
多个动画变量

如果需要同时动画化多个值(如两个或更多),可以用 AnimatablePair:

struct AnimatableArc: Shape {
    var startAngle: Angle
    var endAngle: Angle
    
    var animatableData: AnimatablePair<Double, Double> { // 两个值使用 AnimatablePair
        get { AnimatablePair(startAngle.radians, endAngle.radians) }
        set {
            startAngle = .radians(newValue.first)
            endAngle = .radians(newValue.second)
        }
    }
}
更多动画变量

如果需要动画化三个或更多值,可以嵌套 AnimatablePair:

struct AnimatableShape: Shape {
    var x: Double
    var y: Double
    var scale: Double
    
    var animatableData: AnimatablePair<Double, AnimatablePair<Double, Double>> {
        get { AnimatablePair(x, AnimatablePair(y, scale)) }
        set {
            x = newValue.first
            y = newValue.second.first
            scale = newValue.second.second
        }
    }
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let center = CGPoint(x: rect.midX + CGFloat(x), y: rect.midY + CGFloat(y))
        path.addEllipse(in: CGRect(x: center.x, y: center.y, width: 100 * scale, height: 100 * scale))
        return path
    }
}

扩展总结

1、animatableData 的作用

定义了哪些数据可以动画化。

SwiftUI 的动画系统会在动画期间逐步更新这些数据。

2、AnimatablePair 的使用规则

单变量:直接使用单一类型(如 CGFloat、Double)的 animatableData。

两个变量:使用 AnimatablePair,例如 (startAngle, endAngle)。

多个变量:通过嵌套 AnimatablePair 表达任意数量的值。

3、为什么要这么设计?

SwiftUI 的动画系统需要明确的数据结构来计算和插值。

AnimatablePair 和 animatableData 为复杂的动画提供了灵活性,同时保持易用性和性能。

这让 SwiftUI 能够通过一套统一的动画机制支持从简单到复杂的动画场景。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注