swiftUI 中的逐帧动画

frame by frame animation in swiftUI

我很喜欢使用 swiftUI,但感觉它的动画引擎有限。我有一个图像,我想通过一系列 png 图像制作动画,但我找不到这个的默认实现(或使用隐式、显式或过渡动画的方式)。我是否需要构建一个自定义系统来为该动画的每一帧重绘整个 UI,或者是否有内置的方法来执行此操作?预先感谢您的帮助

好的,所以我在本机 swift 情节提要中做到了这一点,但它的概念相同。在 SwiftUI 中,我从情节提要中翻译的内容与此类似:

var index = 0
var imageArray = ["image1", "image2", "image3"]
Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block:
    (timer) in {
        index++
        if (index >= imageArray.count) {
            index = 0
        }
        self.background(Image("\(imageArray[index])").resizable()
    }
)

这将每 2 秒根据之前定义的数组切换放入的任何内容的背景图像。

希望这能解决您的问题!

对于动画,如果您想要 逐帧 可操作性,我建议您使用 CADisplayLink 将函数调用同步到屏幕的帧速率.这是一个非常强大的工具,尤其是对于动画,因此有了 CA - CoreAnimation。

虽然灵活,但您不能直接在 SwiftUI View 上使用它,因为 View 是一个协议而不是 class,它不允许 [=15] =] 功能。所以你必须创建一个 class/VM 来管理动画。一个低级的例子如下

class ImageAnimator: ObservableObject {

    // Image to add animation/transitions
    @Published var image: Image = Image()

    private var link: CADisplayLink!

    init() {
        // Adding the link to a runloop
        link = CADisplayLink(target: self, selector: #selector(update))
        link.add(to: .current, forMode: RunLoop.Mode.default)
    }

    @objc func update() {
        // Do something
    }

    deinit {
        // Removes link from all runloops
        link.invalidate()
    }
}

这是一个基于 Timer 的解决方案,它有助于循环遍历图像数组并仅 cancel 计时器停止动画。这里的过渡类型 .scale 用于演示。

struct ContentView: View {
    
    let images = ["img1", "img2", "img3", "img1"]
    
    @State private var idx = 0
    
    private let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
    
    var transition: AnyTransition = .scale
    
    var body: some View {
        ZStack {
            ForEach(images.indices, id: \.self) { index in
                if self.idx == index {
                    Image(self.images[index])
                        .transition(transition)
                }
            }
            
        }.onReceive(timer) { _ in
            withAnimation {
                self.idx = self.idx < (self.images.count - 1) ? self.idx + 1 : 0
            }
        }
    }
}