在 SwiftUI 中将 spring 动画与 easyInOut 相结合

问题描述 投票:0回答:2

我正在水平制作一条线的动画,它工作得很好,除了我想要的效果是在它交替两侧之前有轻微的弹性弹跳。我认为

.easeInOut
效果会模仿这一效果,但感觉没有弹性。如何组合
.spring
动画,使线在移动到另一侧之前在每一端弹跳几分之一秒?

struct Line: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: 0, y: rect.size.height / 2))
        path.addLine(to: CGPoint(x: rect.size.width, y: rect.size.height / 2))
        return path
    }
}

struct LineView: View {
    let height: CGFloat
    @State private var animating = false
    
    var body: some View {
        GeometryReader { geometry in
            let lineWidth = geometry.size.width / 3
            
            Line()
                .stroke(Color.black, lineWidth: height)
                .frame(width: lineWidth)
                .offset(x: animating ? 0 : geometry.size.width - lineWidth)
                .animation(.easeInOut(duration: 1.0).repeatForever(), value: animating)
                .onAppear {
                    animating.toggle()
                }
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            LineView(height: 5.0)
                .frame(width: 200)
        }
    }
}

如有任何帮助,我们将不胜感激。

预期效果(蓝线):https://streamable.com/ijwe1w

ios swift swiftui
2个回答
0
投票

您的方向是正确的,但您的解决方案设计过度了。本质上,您需要制作 3 层。第一个是用户感知的背景,但它实际上是基本视图。最重要的是,您添加视觉效果。为了简单起见,我使用了胶囊。最顶层是一个面具,这就是魔法发生的地方。

为了实现您想要的外观,中间层的偏移量应该超过底层,超出您希望它“收缩”的程度。然后,您在顶部放置一个与底层大小相同的遮罩,作为一个窗口,只允许用户看到下面发生的大部分情况。我相信您正在寻找以下内容:

struct LineView: View {
    let height: CGFloat
    @State private var animating = false
    
    var body: some View {
        GeometryReader { geometry in
            Capsule() // Base layer
                .fill(.gray.opacity(0.3))
                .frame(height: height + 2)
                .overlay(alignment: .leading) {
                    let lineWidth = geometry.size.width / 3
                    Capsule()
                        .fill(.blue)
                        .frame(width: lineWidth)
                        // Here I am overshooting by half the line width in both directions
                        .offset(x: animating ? -(lineWidth / 2) : geometry.size.width - lineWidth / 2)
                        .animation(.easeInOut(duration: 1.0).repeatForever(), value: animating)
                }
                // Then mask over with a FillStyle(eoFill: true)
                .mask(
                    Capsule()
                        .fill(style: FillStyle(eoFill: true))
                        .frame(height: height + 2)
                )
                .onAppear {
                    animating.toggle()
                }
                .frame(maxHeight: .infinity)
        }
    }
}

最后,你会得到这个:

enter image description here

(注意,这是一个gif,并不像实际动画那么流畅)


0
投票

如果您只是对前导和尾随填充进行动画处理并在两侧使用负填充,效果会非常好。然后需要对形状进行裁剪,因此负填充的效果是使线条看起来更短。

实际上没有必要使用

Line
形状。您可以仅使用
Rectangle
作为填充形状和剪辑形状。或者,如果您想要圆角,请使用
Capsule

struct LineView: View {
    let height: CGFloat
    @State private var animating = false

    var body: some View {
        GeometryReader { geometry in
            let w = geometry.size.width
            let maxWidth = w / 3
            let minWidth = maxWidth / 2
            Capsule()
                .fill(.blue)
                .padding(.leading, animating ? minWidth - maxWidth : w - minWidth)
                .padding(.trailing, animating ? w - minWidth : minWidth - maxWidth)
                .clipShape(Capsule())
                .frame(height: height)
                .frame(maxHeight: .infinity)
                .animation(.easeInOut(duration: 1.0).repeatForever(), value: animating)
                .onAppear {
                    animating.toggle()
                }
        }
    }
}

Animation

© www.soinside.com 2019 - 2024. All rights reserved.