SwiftUI使用Paths和AnimatableData实现酷炫的颜色切换动画

2020-05-10 19:58:05于海丽

circle 动画

如果你还记得小学几何知识,就应该了解勾股定理。 a^2 + b^2 = c^2

a 和 b 可以视为矩形的 高度 和 宽度 ,我们能够根据它们求得 c ,即覆盖整个矩形所需的圆的半径。我们以此为基础构建圆的 path,并使用 progress 变量随时间对它进行变换。

func circle(rect: CGRect) -> Path {
 let a: CGFloat = rect.height / 2.0
 let b: CGFloat = rect.width / 2.0

 let c = pow(pow(a, 2) + pow(b, 2), 0.5) // a^2 + b^2 = c^2 --> Solved for 'c'
 // c = radius of final circle

 let radius = c * progress
 // Build Circle Path
 var path = Path()
 path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: radius, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: true)
 return path
}

 

angle 动画

这个动画知识点有点多。你需要使用切线计算角度的斜率,然后根据这个斜率创建一条直线。在矩形上移动这条直线时,根据它来绘制一个直角三角形。参见下图,各种彩色的线表示该线随时间移动时,覆盖整个矩形的状态。

方法如下:

func angle(rect: CGRect, angle: Angle) -> Path {
 
 var cAngle = Angle(degrees: angle.degrees.truncatingRemainder(dividingBy: 90))

 // Return Path Using Other Animations (topToBottom, leftToRight, etc) if angle is 0, 90, 180, 270
 if angle.degrees == 0 || cAngle.degrees == 0 { return leftToRight(rect: rect)}
 else if angle.degrees == 90 || cAngle.degrees == 90 { return topToBottom(rect: rect)}
 else if angle.degrees == 180 || cAngle.degrees == 180 { return rightToLeft(rect: rect)}
 else if angle.degrees == 270 || cAngle.degrees == 270 { return bottomToTop(rect: rect)}


 // Calculate Slope of Line and inverse slope
 let m = CGFloat(tan(cAngle.radians))
 let m_1 = pow(m, -1) * -1
 let h = rect.height
 let w = rect.width

 // tan (angle) = slope of line
 // y = mx + b ---> b = y - mx ~ 'b' = y intercept
 let b = h - (m_1 * w) // b = y - (m * x)

 // X and Y coordinate calculation
 var x = b * m * progress
 var y = b * progress

 // Triangle Offset Calculation
 let xOffset = (angle.degrees > 90 && angle.degrees < 270) ? rect.width : 0
 let yOffset = (angle.degrees > 180 && angle.degrees < 360) ? rect.height : 0

 // Modify which side the triangle is drawn from depending on the angle
 if angle.degrees > 90 && angle.degrees < 180 { x *= -1 }
 else if angle.degrees > 180 && angle.degrees < 270 { x *= -1; y *= -1 }
 else if angle.degrees > 270 && angle.degrees < 360 { y *= -1 }

 // Build Triangle Path
 var path = Path()
 path.move(to: CGPoint(x: xOffset, y: yOffset))
 path.addLine(to: CGPoint(x: xOffset + x, y: yOffset))
 path.addLine(to: CGPoint(x: xOffset, y: yOffset + y))
 path.closeSubpath()
 return path

}