SwiftUI 中的插入/移除动画

Posted

技术标签:

【中文标题】SwiftUI 中的插入/移除动画【英文标题】:Insertion/Removal animation in SwiftUI 【发布时间】:2021-04-21 08:34:53 【问题描述】:

我正在尝试实现此动画以在VStack 中插入元素,所有元素向下移动以为新元素创建空间,然后从右向左插入新元素。 下面的代码按照我的描述进行插入,但是如何使它也可以用于移除,动画应该类似,元素被完全移除(向右),然后其他元素向上移动)

struct ContentView: View 
    
    @State var show = false
    
    var body: some View 
        VStack 
            
            show ? Rectangle().frame(width: 100, height: 100, alignment: .center)
                .animation(Animation.easeIn(duration: 0.5).delay(1))
                .transition(.move(edge: .trailing)): nil
            
            Rectangle().frame(width: 100, height: 100, alignment: .center)
            Rectangle().frame(width: 100, height: 100, alignment: .center)
            Rectangle().frame(width: 100, height: 100, alignment: .center)
            Rectangle().frame(width: 100, height: 100, alignment: .center)
            
            Button("Add") 
                show.toggle()
            
            Spacer()
            
        .animation(Animation.easeIn(duration: 0.5))
    

【问题讨论】:

【参考方案1】:

如果你有那么多Rectangle()s,你应该使用ForEach。这将使添加和删除它们变得更加容易,因为您只需 appendremoveLast 即可更改显示的数量。

这就是我所做的:

    创建一个表示Rectangle 的新结构 added 属性将控制是否显示Rectangle 定义将向用户显示的RectangleInfo 数组 使用ForEach 循环遍历rectangles 除了transition,您可以使用offset 来更好地控制动画。

设置好 UI 后,就该制作动画了:

    首先附加一个矩形,为其腾出空间 添加后,将 added 设置为 true,以便它滑入 将 added 设置为 false 以将其滑出 一旦滑出,将其从阵列中移除并移除空间。
struct RectangleInfo: Identifiable  /// 1.
    let id = UUID()
    var added = true /// 2.


struct ContentView: View 
    
    @State var rectangles = [ /// 3.
        RectangleInfo(),
        RectangleInfo(),
        RectangleInfo()
    ]
    
    let animationDuration = 0.5
    
    var body: some View 
        VStack 
            
            ForEach(rectangles)  rectangle in /// 4.
                Rectangle().frame(width: 100, height: 100, alignment: .center)
                    .offset(x: rectangle.added ? 0 : 200, y: 0) /// 5.
            
            
            Button("Add") 
                rectangles.append(RectangleInfo(added: false)) /// 6.
                DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) 
                    rectangles[rectangles.count - 1].added = true /// 7.
                
            
            Button("Remove") 
                rectangles[rectangles.count - 1].added = false /// 8.
                DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) 
                    rectangles.removeLast() /// 9.
                
            
            
            Spacer()
            
        .animation(Animation.easeIn(duration: animationDuration))
    

结果:

【讨论】:

【参考方案2】:

我不确定您的最终结果究竟应该是什么,而且我提出的解决方案并不是真正的“通用”,但它至少应该为您提供一个起点。

struct ContentView: View 
    
    @State var show = false
    @State var showPlaceholder = false
    
    // The total animation duration will be twice as long as this value
    private var animationDuration: TimeInterval = 0.5
    
    var body: some View 
        VStack 
            
            ZStack 
                if showPlaceholder 
                    Color.white.opacity(0.0)
                        .zIndex(1)
                        .frame(width: 100, height: 100)
                
                if show 
                    Rectangle()
                        .zIndex(2)
                        .frame(width: 100, height: 100, alignment: .center)
                        .animation(Animation.easeIn(duration: animationDuration))
                    .transition(.move(edge: .trailing))
                
            
            
            Rectangle().frame(width: 100, height: 100, alignment: .center)
            Rectangle().frame(width: 100, height: 100, alignment: .center)
            Rectangle().frame(width: 100, height: 100, alignment: .center)
            Rectangle().frame(width: 100, height: 100, alignment: .center)
            
            Button("Add") 
                if show 
                    showPlaceholder(for: animationDuration)
                    show = false
                
                else 
                    showPlaceholder(for: animationDuration) 
                        show = true
                    
                
            
            Spacer()
            
        .animation(Animation.easeIn(duration: animationDuration))
    
    
    private func showPlaceholder(for timeInterval: TimeInterval, completion: (() -> Void)? = nil) 
        showPlaceholder = true
        Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false)  (_) in
            showPlaceholder = false
            completion?()
        
    

【讨论】:

以上是关于SwiftUI 中的插入/移除动画的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 的 ScrollView 中制作动画? SwiftUI 中的手风琴风格动画

SwiftUI 中的动画状态变化

SwiftUI TabView 动画

如何动画视图插入/删除的过渡?插入动画不起作用

SwiftUI:删除列表中的动画?

SwiftUI:动画列表中的圆圈