Animate a UIView along a part of a bezier path

2019-04-11 18:43发布

问题:

I'm trying animate a UIView along a portion of a bezier path. I found a way to move the the view to any part of the path using this code:

let animation = CAKeyframeAnimation(keyPath: "position")

animation.path = trackPath.cgPath

animation.rotationMode = kCAAnimationRotateAuto
animation.speed = 0
animation.timeOffset = offset
animation.duration = 1
animation.calculationMode = kCAAnimationPaced
square.layer.add(animation, forKey: "animate position along path")

However, this just moves the view to the desired point and doesn't animate it. How do you animate a view along over a portion of a bezier path?

Thanks

回答1:

You can accomplish this by modifying the timing of the "complete" animation and a animation group that wraps it.

To illustrate how the timing of such an animation works, imagine – instead of animating a position along a path – that a color is being animated. Then, the complete animation from one color to another can be illustrated as this, where a value further to the right is at a later time — so that the very left is the start value and the very right is the end value:

Note that the "timing" of this animation is linear. This is intentional since the "timing" of the end result will be configured later.

In this animation, imagine that we're looking to animate only the middle third, this part of the animation:

There are a few steps to animating only this part of the animation.

First, configure the "complete" animation to have linear timing (or in the case of an animation along a path; to have a paced calculation mode) and to have a the "complete" duration.

For example: if you're looking to animate a third of the animation an have that take 1 second, configure the complete animation to take 3 seconds.

let relativeStart = 1.0/3.0
let relativeEnd   = 2.0/3.0

let duration      = 1.0
let innerDuration = duration / (relativeEnd - relativeStart) // 3 seconds

// configure the "complete" animation
animation.duration = innerDuration

This means that the animation currently is illustrated like this (the full animation):

Next, so that the animation "starts" a third of the way into the full animation, we "offset" its time by a third of the duration:

animation.timeOffset = relativeStart * innerDuration

Now the animation is illustrated like this, offset and wrapping from its end value to its start value:

Next, so that we only display part of this animation, we create an animation group with the wanted duration and add only the "complete" animation to it.

let group = CAAnimationGroup()
group.animations = [animation]
group.duration = duration

Even though this group contains an animation that is 3 seconds long it will end after just 1 second, meaning that the 2 seconds of the offset "complete" animation will never be shown.

Now the group animation is illustrated like this, ending after a third of the "complete" animation:

If you look closely you'll see that this (the part that isn't faded out) is the middle third of the "complete" animation.

Now that this group animates animates between the wanted values, it (the group) can be configured further and then added to a layer. For example, if you wanted this "partial" animation to reverse, repeat, and have a timing curve you would configure these things on the group:

group.repeatCount = HUGE
group.autoreverses = true
group.timingFunction = CAMediaTimingFunction(name: "easeInEaseOut")

With this additional configuration, the animation group would be illustrated like this:


As a more concrete example, this is an animation that I created using this technique (in fact all the code is from that example) that moves a layer back and forth like a pendulum. In this case the "complete" animation was a "position" animation along a path that was a full circle



回答2:

I've done a similar animation just a few days ago:

// I want the animation to go along the circular path of the oval shape
let flightAnimation = CAKeyframeAnimation(keyPath: "position")
flightAnimation.path = ovalShapeLayer.path
// I set this one to make the animation go smoothly along the path
flightAnimation.calculationMode = kCAAnimationPaced
flightAnimation.duration = 1.5
flightAnimation.rotationMode = kCAAnimationRotateAuto
airplaneLayer.add(flightAnimation, forKey: nil)

I see that you set speed to zero and a time offset. Why do you need them? I would suggest to try the animation using just the parameters in the above code and then try to tune it from them.