Прерывание анимации SwiftUI жестом перетаскивания
В этом году Apple упростила плавную анимацию изображения после выполнения жеста перетаскивания, автоматически используя конечную скорость жеста перетаскивания в качестве начальной скорости анимации. Вот простой пример:
struct DraggableCircle: View {
@State private var offset: CGSize = .zero
var body: some View {
let gesture = DragGesture(minimumDistance: 0)
.onChanged { gesture in
offset = gesture.translation
}
.onEnded { _ in
withAnimation(.bouncy(duration: 2)) {
offset = .zero
}
}
Circle()
.frame(width: 200, height: 200)
.offset(offset)
.gesture(gesture)
}
}
Однако этому коду не хватает возможности плавно прерывать анимацию круга, начиная новый жест перетаскивания до завершения анимации. Вместо этого кружок мгновенно завершит анимацию, отскочив от вашего пальца:
Это имеет смысл, поскольку мы устанавливаемoffset
вернуться к.zero
когда жест заканчивается, не делая ничего, чтобы выяснить, где на экране находился круг, когда анимация была прервана.
Каков наиболее простой способ заставить эту работу работать без резких скачков?
Один из возможных подходов — расположить круг, используя положение жеста, а не его перевод, при этом круг всегда помещается в то место, где происходит жест:
struct DraggableCircle_v2: View {
@State private var position: CGPoint?
var body: some View {
let gesture = DragGesture(minimumDistance: 0)
.onChanged { gesture in
position = gesture.location
}
.onEnded { _ in
withAnimation(.bouncy(duration: 2)) {
position = nil
}
}
GeometryReader { geo in
let center = CGPoint(
x: geo.size.width / 2,
y: geo.size.height / 2
)
Circle()
.frame(width: 200, height: 200)
.position(position ?? center)
.gesture(gesture)
}
}
}
Это будет работать для большинства случаев использования. Однако при этом центр круга всегда оказывается там, где находится ваш палец, не принимая во внимание, где на круге начался жест. Другими словами, мы не продвинулись к цели выяснить, где находится круг в момент прерывания анимации.
Можем ли мы прервать нашу анимацию жестом и выяснить, где находится круг в момент прерывания?
Изменить : Вот чего я пытаюсь достичь:
1 ответ
Давайте используем отдельную переменную для отслеживания положения круга при перетаскивании (dragOffset) и отдельную переменную для анимации возврата круга в исходное положение (animationOffset). Мы будем использовать эти две переменные отдельно, чтобы добиться плавного поведения.
struct ContentViewC: View {
var body: some View {
ZStack {
Color.white.edgesIgnoringSafeArea(.all)
DraggableCircleView()
}
}
}
struct DraggableCircleView: View {
@GestureState private var dragOffset: CGSize = .zero
@State private var animationOffset: CGSize = .zero
var body: some View {
VStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(.blue)
.offset(animationOffset) // Use animationOffset for smooth animation
.offset(dragOffset) // Use dragOffset for smooth dragging
.gesture(DragGesture()
.updating($dragOffset, body: { (value, dragOffset, _) in
dragOffset = value.translation
})
.onEnded { value in
// Save the final drag position in animationOffset
animationOffset = value.translation
withAnimation(.easeOut(duration: 1.5)) {
// Animate the circle back to its initial position
animationOffset = .zero
}
}
)
}
}}
В этом коде мы используем dragOffset для обновления положения круга во время перетаскивания и анимациюOffset для плавной анимации после окончания перетаскивания. Такое разделение должно предотвратить эффект подпрыгивания и обеспечить более плавное перетаскивание и анимацию круга обратно в центр.
Если у вас есть два модификатора .offset в иерархии представлений, SwiftUI объединяет их и применяет общее смещение к представлению.
Вы можете поменять смещение 2x для удобства чтения с помощью:
.offset(CGSize(width: animationOffset.width + dragOffset.width,
height: animationOffset.height + dragOffset.height))
Но 2-кратное смещение выглядит более чистым.