How can I get the blue circles to first move away from the green circle before getting back to it?
The animation should be:
- press and hold:
- the green circle scales down
- the blue circles, while scaling down as well, first move "up" (away from their resting position, as if pushed away by the pressure applied) and then down (to touch the green circle again, as if they were pulled back by some gravitational force)
- release
- everything springs back into place
- (bonus) ideally, the blue circles are ejected as the green circle springs up, and they fall back down in their resting position (next to the surface)
I got everything working except the blue circles moving up part.
This is the current animation:
And its playground.
import SwiftUI
import PlaygroundSupport
struct DotsView: View {
var diameter: CGFloat = 200
var size: CGFloat = 25
var isPressed: Bool = false
var body: some View {
ZStack {
ForEach(0...5, id: \.self) { i in
Circle()
.fill(Color.blue)
.frame(width: size, height: size)
.offset(x: 0, y: -(diameter)/2 - size/2)
.rotationEffect(.degrees(CGFloat(i * 60)))
}
}
.frame(width: diameter, height: diameter)
.animation(.none)
.scaleEffect(isPressed ? 0.8 : 1)
.animation(
isPressed ? .easeOut(duration: 0.2) : .interactiveSpring(response: 0.35, dampingFraction: 0.2),
value: isPressed
)
.background(
Circle()
.fill(Color.green)
.scaleEffect(isPressed ? 0.8 : 1)
.animation(isPressed ? .none : .interactiveSpring(response: 0.35, dampingFraction: 0.2), value: isPressed)
)
}
}
struct ContentView: View {
@State private var isPressed: Bool = false
var body: some View {
DotsView(
diameter: 200,
isPressed: isPressed
)
.frame(width: 500, height: 500)
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in
isPressed = true
}
.onEnded { _ in
isPressed = false
}
)
}
}
let view = ContentView()
PlaygroundPage.current.setLiveView(view)
Thanks
CodePudding user response:
Actually all is needed is to replace linear scaleEffect
with custom geometry effect that gives needed scale curve (initially growing then falling).
Here is a demo of possible approach (tested with Xcode 13.4 / iOS 15.5)
Main part:
struct JumpyEffect: GeometryEffect {
let offset: Double
var value: Double
var animatableData: Double {
get { value }
set { value = newValue }
}
func effectValue(size: CGSize) -> ProjectionTransform {
let trans = (value offset * (pow(5, value - 1/pow(value, 5))))
let transform = CGAffineTransform(translationX: size.width * 0.5, y: size.height * 0.5)
.scaledBy(x: trans, y: trans)
.translatedBy(x: -size.width * 0.5, y: -size.height * 0.5)
return ProjectionTransform(transform)
}
}
and usage
.modifier(JumpyEffect(offset: isPressed ? 0.3 : 0, value: isPressed ? 0.8 : 1))