【发布时间】:2019-09-21 03:48:02
【问题描述】:
为按钮设置凹凸效果的简单而常规的方法,但在 SwiftUI 中并不简单。
我正在尝试在 tapGesture 修饰符中更改 scale,但它没有任何效果。我不知道如何制作动画链,可能是因为 SwiftUI 没有它。所以我幼稚的做法是:
@State private var scaleValue = CGFloat(1)
...
Button(action: {
withAnimation {
self.scaleValue = 1.5
}
withAnimation {
self.scaleValue = 1.0
}
}) {
Image("button1")
.scaleEffect(self.scaleValue)
}
显然它不起作用,按钮图像立即获得最后一个比例值。
我的第二个想法是在hold 事件上将比例更改为0.8 值,然后在release 事件之后将比例更改为1.2,并在几毫秒后再次将其更改为1.0。我想这个算法应该会产生更好更自然的 bump 效果。但是我在 SwiftUI 中找不到合适的 gesture 结构来处理 hold-n-release 事件。
附:为了便于理解,我将描述hold-n-release算法的步骤:
- 刻度值为
1.0 - 用户触摸按钮
- 按钮刻度变为
0.8 - 用户松开按钮
- 按钮刻度变为
1.2 - 延迟
0.1秒 - 按钮刻度恢复默认
1.0
UPD:我找到了一个使用动画delay 修饰符的简单解决方案。但我不确定它是否正确和清晰。它也不涵盖hold-n-release 问题:
@State private var scaleValue = CGFloat(1)
...
Button(action: {
withAnimation {
self.scaleValue = 1.5
}
//
// Using delay for second animation block
//
withAnimation(Animation.linear.delay(0.2)) {
self.scaleValue = 1.0
}
}) {
Image("button1")
.scaleEffect(self.scaleValue)
}
UPD 2:
我注意到在上面的解决方案中,我将什么值作为参数传递给 delay 修饰符并不重要:0.2 或 1000 将具有相同的效果。也许这是一个错误????
所以我使用了Timer 实例而不是delay 动画修饰符。现在它按预期工作:
...
Button(action: {
withAnimation {
self.scaleValue = 1.5
}
//
// Replace it
//
// withAnimation(Animation.linear.delay(0.2)) {
// self.scaleValue = 1.0
// }
//
// by Timer with 0.5 msec delay
//
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
withAnimation {
self.scaleValue = 1.0
}
}
}) {
...
UPD 3:
在我们等待苹果官方更新之前,实现touchStart 和touchEnd 两个事件的合适解决方案之一是基于@average Joe answer:
import SwiftUI
struct TouchGestureViewModifier: ViewModifier {
let minimumDistance: CGFloat
let touchBegan: () -> Void
let touchEnd: (Bool) -> Void
@State private var hasBegun = false
@State private var hasEnded = false
init(minimumDistance: CGFloat, touchBegan: @escaping () -> Void, touchEnd: @escaping (Bool) -> Void) {
self.minimumDistance = minimumDistance
self.touchBegan = touchBegan
self.touchEnd = touchEnd
}
private func isTooFar(_ translation: CGSize) -> Bool {
let distance = sqrt(pow(translation.width, 2) + pow(translation.height, 2))
return distance >= minimumDistance
}
func body(content: Content) -> some View {
content.gesture(DragGesture(minimumDistance: 0)
.onChanged { event in
guard !self.hasEnded else { return }
if self.hasBegun == false {
self.hasBegun = true
self.touchBegan()
} else if self.isTooFar(event.translation) {
self.hasEnded = true
self.touchEnd(false)
}
}
.onEnded { event in
if !self.hasEnded {
let success = !self.isTooFar(event.translation)
self.touchEnd(success)
}
self.hasBegun = false
self.hasEnded = false
}
)
}
}
extension View {
func onTouchGesture(minimumDistance: CGFloat = 20.0,
touchBegan: @escaping () -> Void,
touchEnd: @escaping (Bool) -> Void) -> some View {
modifier(TouchGestureViewModifier(minimumDistance: minimumDistance, touchBegan: touchBegan, touchEnd: touchEnd))
}
}
【问题讨论】: