如何解决 SwiftUI 中的破折号阶段动画缺陷?

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

我想为破折号阶段设置动画,但我遇到了动画缺陷。如何解决这些缺陷,以实现给定线宽的平滑且连续的动画?

enter image description here

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()
            }
        
    }
}
swift swiftui
1个回答
0
投票

这种缺陷是因为虚线没有完全填满路径的整个长度。如果您使用的破折号大小是路径长度的精确因子,则看起来不错。例如,

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)
© www.soinside.com 2019 - 2024. All rights reserved.