我正在水平制作一条线的动画,它工作得很好,除了我想要的效果是在它交替两侧之前有轻微的弹性弹跳。我认为
.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
您的方向是正确的,但您的解决方案设计过度了。本质上,您需要制作 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)
}
}
}
最后,你会得到这个:
(注意,这是一个gif,并不像实际动画那么流畅)
如果您只是对前导和尾随填充进行动画处理并在两侧使用负填充,效果会非常好。然后需要对形状进行裁剪,因此负填充的效果是使线条看起来更短。
实际上没有必要使用
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()
}
}
}
}