我正在尝试为 .contentShape 设置动画,其形状为使用 Bezier 路径构建的 Chat Bubble 到 Rectangle。
环顾四周,我发现我需要在我的 Shape 中定义 animatableData 才能工作。但是,我只能弄清楚如何为两点设置动画。我需要为多个点设置动画。
例如,我在下面有一个简单的矩形。我怎样才能使这个动画超过两点?
下面的示例我设法为两个点设置动画,它适用于这两个点。但在这里,我实际上需要为所有三个点(二、三和四)设置动画才能正常工作。
我对这一切都错了吗?是否有另一种更简单的方法来动画从一个形状到另一个形状的复杂路径?
下面首先是一个矩形示例,我可以在其中设置两个点的动画。下面是我的聊天气泡定义,我想将其动画化为一个普通的矩形。它在 .contentShape 中用于聊天应用程序中的图像。
struct CustomRectangleShape: Shape {
init(width: CGFloat, height: CGFloat) {
self.two = CGPoint(x: width, y: 0)
self.three = CGPoint(x: width, y: height)
self.four = CGPoint(x: 0, y: height)
}
var start = CGPoint(x: 0, y: 0)
var two: CGPoint
var three: CGPoint
var four: CGPoint
var end = CGPoint(x: 0, y: 0)
var animatableData: AnimatablePair<CGPoint.AnimatableData, CGPoint.AnimatableData> {
get { AnimatablePair(two.animatableData, three.animatableData) }
set { (two.animatableData, three.animatableData) = (newValue.first, newValue.second) }
}
func path(in rect: CGRect) -> Path {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: two)
path.addLine(to: three)
path.addLine(to: four)
path.addLine(to: end)
return Path(path.cgPath)
}
}
struct ContentView: View {
@State var smallRect = false
var body: some View {
VStack {
Button {
withAnimation(.easeInOut(duration: 2)) {
smallRect.toggle()
}
} label: {
Text("Change clip shape")
}
Image("leaf")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 300)
.clipShape(smallRect ? CustomRectangleShape(width: 150, height: 150) : CustomRectangleShape(width: 300, height: 300))
}
}
}
这是我要转换为矩形的形状。此形状来自: https ://recreatecode.substack.com/p/recreate-the-messages-app-ios-14-67b
struct BubbleShape: Shape {
var myMessage : Bool
func path(in rect: CGRect) -> Path {
let width = rect.width
let height = rect.height
let bezierPath = UIBezierPath()
if !myMessage {
bezierPath.move(to: CGPoint(x: 20, y: height))
bezierPath.addLine(to: CGPoint(x: width - 15, y: height))
bezierPath.addCurve(to: CGPoint(x: width, y: height - 15), controlPoint1: CGPoint(x: width - 8, y: height), controlPoint2: CGPoint(x: width, y: height - 8))
bezierPath.addLine(to: CGPoint(x: width, y: 15))
bezierPath.addCurve(to: CGPoint(x: width - 15, y: 0), controlPoint1: CGPoint(x: width, y: 8), controlPoint2: CGPoint(x: width - 8, y: 0))
bezierPath.addLine(to: CGPoint(x: 20, y: 0))
bezierPath.addCurve(to: CGPoint(x: 5, y: 15), controlPoint1: CGPoint(x: 12, y: 0), controlPoint2: CGPoint(x: 5, y: 8))
bezierPath.addLine(to: CGPoint(x: 5, y: height - 10))
bezierPath.addCurve(to: CGPoint(x: 0, y: height), controlPoint1: CGPoint(x: 5, y: height - 1), controlPoint2: CGPoint(x: 0, y: height))
bezierPath.addLine(to: CGPoint(x: -1, y: height))
bezierPath.addCurve(to: CGPoint(x: 12, y: height - 4), controlPoint1: CGPoint(x: 4, y: height + 1), controlPoint2: CGPoint(x: 8, y: height - 1))
bezierPath.addCurve(to: CGPoint(x: 20, y: height), controlPoint1: CGPoint(x: 15, y: height), controlPoint2: CGPoint(x: 20, y: height))
} else {
bezierPath.move(to: CGPoint(x: width - 20, y: height))
bezierPath.addLine(to: CGPoint(x: 15, y: height))
bezierPath.addCurve(to: CGPoint(x: 0, y: height - 15), controlPoint1: CGPoint(x: 8, y: height), controlPoint2: CGPoint(x: 0, y: height - 8))
bezierPath.addLine(to: CGPoint(x: 0, y: 15))
bezierPath.addCurve(to: CGPoint(x: 15, y: 0), controlPoint1: CGPoint(x: 0, y: 8), controlPoint2: CGPoint(x: 8, y: 0))
bezierPath.addLine(to: CGPoint(x: width - 20, y: 0))
bezierPath.addCurve(to: CGPoint(x: width - 5, y: 15), controlPoint1: CGPoint(x: width - 12, y: 0), controlPoint2: CGPoint(x: width - 5, y: 8))
bezierPath.addLine(to: CGPoint(x: width - 5, y: height - 12))
bezierPath.addCurve(to: CGPoint(x: width, y: height), controlPoint1: CGPoint(x: width - 5, y: height - 1), controlPoint2: CGPoint(x: width, y: height))
bezierPath.addLine(to: CGPoint(x: width + 1, y: height))
bezierPath.addCurve(to: CGPoint(x: width - 12, y: height - 4), controlPoint1: CGPoint(x: width - 4, y: height + 1), controlPoint2: CGPoint(x: width - 8, y: height - 1))
bezierPath.addCurve(to: CGPoint(x: width - 20, y: height), controlPoint1: CGPoint(x: width - 15, y: height), controlPoint2: CGPoint(x: width - 20, y: height))
}
return Path(bezierPath.cgPath)
}
}
并使用:
Image("leaf")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 300)
.clipShape(.clipShape(BubbleShape(myMessage: true)) // ideally I want .clipShape(.clipShape(showImageRect ? Rectangle() : BubbleShape(myMessage: true))