如何在SwiftUI中自定义角度变化的动画
我有一个应用程序,显示了一群人,每个人都有一个原点和角度如何在SwiftUI中自定义角度变化的动画,swiftui,swiftui-animation,Swiftui,Swiftui Animation,我有一个应用程序,显示了一群人,每个人都有一个原点和角度 struct Location { var centre:CGPoint var facing:Angle } 当SwiftUI从位置a移动到位置B时,它会神奇地自动生成大量动画 withAnimation { person.location = newLocation } 但是,对于“角度(朝向)”属性,我希望动画以最短的路径行进(请记住,在现实世界中,角度环绕) e、 g.当角度改变5->10(度)时,Sw
struct Location {
var centre:CGPoint
var facing:Angle
}
当SwiftUI从位置a移动到位置B时,它会神奇地自动生成大量动画
withAnimation {
person.location = newLocation
}
但是,对于“角度(朝向)”属性,我希望动画以最短的路径行进(请记住,在现实世界中,角度环绕)
e、 g.当角度改变5->10(度)时,Swift UI会正确设置动画
但是从2到358,这需要很长的路
SwiftUI做了2,3,4,5,6,7………,357358
我想在哪里做
2,1,0,359,358
我该怎么办
多谢各位
更新:我希望有一个解决方案可以让我使用动画系统,也许使用一个新的MyAngle结构,它直接提供动画步骤,也许使用某种动画修改器。
.easeInOut修改步骤-是否有等效的方法可以创建.GotherLightway动画?如何调整newLocation值以保持在开始的180˚范围内?下面是一个函数,用于检查设置动画的距离是否大于一半,并提供满足该距离的新端点
func adjustedEnd(from start: CGFloat, to target: CGFloat) -> CGFloat {
// Shift end to be greater than start
var end = target
while end < start { end += 360 }
// Mod the distance with 360, shifting by 180 to keep on the same side of a circle
return (end - start + 180).truncatingRemainder(dividingBy: 360) - 180 + start
}
打印以下内容:
From 2.0:
2.0 becomes 2.0; distance 0.0
10.0 becomes 10.0; distance 8.0
180.0 becomes 180.0; distance 178.0
185.0 becomes -175.0; distance 177.0
350.0 becomes -10.0; distance 12.0
-10.0 becomes -10.0; distance 12.0
715.0 becomes -5.0; distance 7.0
-700.0 becomes 20.0; distance 18.0
From -10.0:
2.0 becomes 2.0; distance 12.0
10.0 becomes 10.0; distance 20.0
180.0 becomes -180.0; distance 170.0
185.0 becomes -175.0; distance 165.0
350.0 becomes -10.0; distance 0.0
-10.0 becomes -10.0; distance 0.0
715.0 becomes -5.0; distance 5.0
-700.0 becomes 20.0; distance 30.0
From 345.0:
2.0 becomes 362.0; distance 17.0
10.0 becomes 370.0; distance 25.0
180.0 becomes 180.0; distance 165.0
185.0 becomes 185.0; distance 160.0
350.0 becomes 350.0; distance 5.0
-10.0 becomes 350.0; distance 5.0
715.0 becomes 355.0; distance 10.0
-700.0 becomes 380.0; distance 35.0
From 365.0:
2.0 becomes 362.0; distance 3.0
10.0 becomes 370.0; distance 5.0
180.0 becomes 540.0; distance 175.0
185.0 becomes 185.0; distance 180.0
350.0 becomes 350.0; distance 15.0
-10.0 becomes 350.0; distance 15.0
715.0 becomes 355.0; distance 10.0
-700.0 becomes 380.0; distance 15.0
From 700.0:
2.0 becomes 722.0; distance 22.0
10.0 becomes 730.0; distance 30.0
180.0 becomes 540.0; distance 160.0
185.0 becomes 545.0; distance 155.0
350.0 becomes 710.0; distance 10.0
-10.0 becomes 710.0; distance 10.0
715.0 becomes 715.0; distance 15.0
-700.0 becomes 740.0; distance 40.0
(编辑以说明负的结束值)
编辑:根据您关于保留第二个值的评论,如何设置位置。将朝向调整后的角度,然后添加到位置,如
var prettyFacing: Angle {
var facing = self.facing
while facing.degrees < 0 { facing += Angle(degrees: 360) }
while facing.degrees > 360 { facing -= Angle(degrees: 360) }
return facing
}
var prettyFacing:角度{
var facing=自面向
当朝向时。度<0{朝向+=角度(度:360)}
当朝向时.degrees>360{朝向-=角度(度:360)}
返回面
}
确定-发布我自己的答案。
它的工作原理有点像@Ben的答案——但将“阴影角度”管理移动到旋转效果
您只需切换rotationEffect(角度:角度)
shortRotationEffect(角度:角度,id:UUID)
这看起来像
Image(systemName: "person.fill").resizable()
.frame(width: 50, height: 50)
.shortRotationEffect(self.person.angle,id:person.id)
.animation(.easeInOut)
ShortRotationEffect使用提供的id维护以前角度的字典。当您设置一个新角度时,它会计算出提供短旋转的等效角度,并使用法线旋转效果(…)
这是:
extension View {
/// Like RotationEffect - but when animated, the rotation moves in the shortest direction.
/// - Parameters:
/// - angle: new angle
/// - anchor: anchor point
/// - id: unique id for the item being displayed. This is used as a key to maintain the rotation history and figure out the right direction to move
func shortRotationEffect(_ angle: Angle,anchor: UnitPoint = .center, id:UUID) -> some View {
modifier(ShortRotation(angle: angle,anchor:anchor, id:id))
}
}
struct ShortRotation: ViewModifier {
static var storage:[UUID:Angle] = [:]
var angle:Angle
var anchor:UnitPoint
var id:UUID
func getAngle() -> Angle {
var newAngle = angle
if let lastAngle = ShortRotation.storage[id] {
let change:Double = (newAngle.degrees - lastAngle.degrees) %% 360.double
if change < 180 {
newAngle = lastAngle + Angle.init(degrees: change)
}
else {
newAngle = lastAngle + Angle.init(degrees: change - 360)
}
}
ShortRotation.storage[id] = newAngle
return newAngle
}
func body(content: Content) -> some View {
content
.rotationEffect(getAngle(),anchor: anchor)
}
}
在尝试了其他两种方法之后,我们仍然会遇到视觉上的问题(虽然不太常见,但仍然存在!)
我们的解决方案:使用UIKit制作动画
我们创建了一个SPM包,它添加了一个简单的修饰符,.uiRotationEffect()
。此修改器将您的视图
包装在ui视图
中,并使用ui视图
的.animate(…)
函数获得正确的行为
你可以安装软件包,也可以复制粘贴代码,它不会很长
工作解决方案的内容:
谢谢你的来电。我已经考虑过这种方法,但它需要维护一组阴影位置,这些阴影位置必须在更改实际位置时进行调整。我希望有一种方法可以直接改变动画逻辑。在动画系统的某个地方——一定有什么东西在计算A和B之间的步骤……啊,我看到了@ConfusedVorlon。人物模型是否需要将角度值保持在0和360之间?如果location.facing被设置为这个调整后的值,然后您将一个计算变量添加到location
,类似于prettyFacing
,在必要时将facing
调整回非负数,该怎么办?请使用示例回答编辑至。对于我意识到的动画问题仍然没有帮助,只是想也许它会有帮助!这样更好,但不太管用。当我旋转一整圈时,它仍然会在360度旋转结束时跳跃。不过,这确实解决了0到360之间发生的跳跃问题。
Image(systemName: "person.fill").resizable()
.frame(width: 50, height: 50)
.shortRotationEffect(self.person.angle,id:person.id)
.animation(.easeInOut)
extension View {
/// Like RotationEffect - but when animated, the rotation moves in the shortest direction.
/// - Parameters:
/// - angle: new angle
/// - anchor: anchor point
/// - id: unique id for the item being displayed. This is used as a key to maintain the rotation history and figure out the right direction to move
func shortRotationEffect(_ angle: Angle,anchor: UnitPoint = .center, id:UUID) -> some View {
modifier(ShortRotation(angle: angle,anchor:anchor, id:id))
}
}
struct ShortRotation: ViewModifier {
static var storage:[UUID:Angle] = [:]
var angle:Angle
var anchor:UnitPoint
var id:UUID
func getAngle() -> Angle {
var newAngle = angle
if let lastAngle = ShortRotation.storage[id] {
let change:Double = (newAngle.degrees - lastAngle.degrees) %% 360.double
if change < 180 {
newAngle = lastAngle + Angle.init(degrees: change)
}
else {
newAngle = lastAngle + Angle.init(degrees: change - 360)
}
}
ShortRotation.storage[id] = newAngle
return newAngle
}
func body(content: Content) -> some View {
content
.rotationEffect(getAngle(),anchor: anchor)
}
}
public extension Double {
/// Returns modulus, but forces it to be positive
/// - Parameters:
/// - left: number
/// - right: modulus
/// - Returns: positive modulus
static func %% (_ left: Double, _ right: Double) -> Double {
let truncatingRemainder = left.truncatingRemainder(dividingBy: right)
return truncatingRemainder >= 0 ? truncatingRemainder : truncatingRemainder+abs(right)
}
}