我想为破折号阶段设置动画,但我遇到了动画缺陷。如何解决这些缺陷,以实现给定线宽的平滑且连续的动画?
import SwiftUI
struct ContentView: View {
let lineWidth: CGFloat = 90.0
var body: some View {
DashedCircleView(lineWidth: lineWidth)
}
}
struct DashedCircleView: View {
let lineWidth: CGFloat
@State private var phaseAnimation: Bool = Bool()
var body: some View {
Circle()
.strokeBorder(
style: StrokeStyle(
lineWidth: lineWidth,
dash: [2*lineWidth],
dashPhase: phaseAnimation ? 4*lineWidth : .zero))
.frame(width: 400, height: 400)
.foregroundColor(Color.red)
.animation(Animation.linear(duration: 1).repeatForever(autoreverses: false), value: phaseAnimation)
.onAppear {
phaseAnimation.toggle()
}
}
}
这种缺陷是因为虚线没有完全填满路径的整个长度。如果您使用的破折号大小是路径长度的精确因子,则看起来不错。例如,
Circle()
.strokeBorder(
style: StrokeStyle(
lineWidth: lineWidth,
dash: [(400 - lineWidth) * .pi / 6],
dashPhase: phaseAnimation ? (400 - lineWidth) * .pi / 3 : .zero
))
.frame(width: 400, height: 400)
400 - lineWidth
是圆的直径。请记住,strokeBorder
将形状插入了 lineWidth / 2
,因此圆的直径要小 lineWidth
。通过将圆的周长除以偶数,我确保虚线完全填满整个路径。
路径的长度可能很难计算,并且您将不得不使用不均匀的破折号大小。您仍然可以通过对路径开始的位置设置动画来使破折号正确移动,因为那是破折号开始的地方。
内置的
Circle
总是从圆圈的右侧开始,这就是为什么奇怪的视觉效果总是发生在那里。
这里我创建了一个
MyCircle
,其起点可以控制,
struct MyCircle: InsettableShape, Animatable {
var startAngle: Angle
let inset: CGFloat
var animatableData: Double {
get { startAngle.radians }
set { startAngle = .radians(newValue) }
}
nonisolated func path(in rect: CGRect) -> Path {
Path { p in
p.addArc(center: .init(x: rect.midX, y: rect.midY), radius: rect.width / 2 - inset, startAngle: startAngle, endAngle: startAngle + .degrees(360), clockwise: false)
}
}
nonisolated func inset(by amount: CGFloat) -> MyCircle {
MyCircle(startAngle: startAngle, inset: inset + amount)
}
}
可以像这样制作动画:
MyCircle(startAngle: phaseAnimation ? .zero : .degrees(360), inset: 0)
.strokeBorder(
style: StrokeStyle(
lineWidth: lineWidth,
// here I use a rather random dash size
// - the dash will not be evenly spread out, but at least they rotate as you'd expect
dash: [2 * lineWidth]
// I am not animating the dash phase here
))
.frame(width: 400, height: 400)