天天看點

CALayer的additive屬性解析Multiple animationsAdditive animations

calayer的additive屬性解析

CALayer的additive屬性解析Multiple animationsAdditive animations

效果:

CALayer的additive屬性解析Multiple animationsAdditive animations
CALayer的additive屬性解析Multiple animationsAdditive animations

源碼:https://github.com/rylanjin/shareofcoreanimation

詳細技術分析:http://ronnqvi.st/multiple-animations/

the “other” way is to update the model value by setting the property being animated to its final value. this means that once the animation finishes and is removed, the model both is and appears to be in the correct state. the question then is: where exactly is it best practice to update the value when adding an animation? before creating the animation? before adding the animation to the layer? or after adding the animation to the object?

the only meaningful distinction to core animation is whether the model is updated before or after adding the explicit animation, so from that perspective, the first and second case can be treated as one case: the “before” case. exactly where before adding the animation that the model is updated only affects how the animation values are configured. if the model is updated before configuring the animation, the old state might need to be saved first, so that the animation can be configured to animate from the old state to the new state. since there is no real technical difference between the two cases (1 and 2), we should only optimize for clarity. i usually find that code that configures the animation before updating the model is easier to follow but there are cases where the opposite is true. in the end it comes down to personal preference.

that leaves us with only two cases to compare: before adding the animation and after adding the animation. this is when things start to get interesting. depending on if we are animating a stand-alone layer or a backing layer (a layer that is attached to a view), we will see some differences between the before and after cases. this has to do with the way that layers implicitly animate property changes and the way that views disable this default layer behavior. before explaining the difference between the two, let’s look at what an implicit animation is and what the default layer behavior is.

when a layer property changes, the layers looks for an appropriate “action” to run for that change. an action is an object that conforms to the <code>caaction</code>protocol. in practice, it’s going to be a subclass of <code>caanimation</code>. the steps that the layer takes to look for the action is described in detail in the documentation for the <code>actionforkey:</code> method on calayer.

the layer starts by asking its delegate which can return with one out of three things:

an action to run

<code>nil</code> to keep looking

<code>nsnull</code> to stop looking

next, the layer looks for the property as the key in its <code>actions</code> dictionary. it can end up in one out of three cases:

an action to run is found for that key.

that key doesn’t exist, telling the layer to keep looking.

<code>nsnull</code> is found for that key, telling the layer to stop looking.

after that the layer looks for an <code>actions</code> dictionary in the <code>style</code> dictionary (something that i’ve never used myself or seen any other developer use). it then checks if there are any specific actions for that class by calling<code>defaultactionforkey:</code>. if no action if found the layer uses the implicit action that is defined by core animation.

in the normal case, we end up with the implicit animation. this animation object is then added to layer, using the <code>addanimation:forkey:</code> method, just like we would add an animation object ourselves. this all happens automatically, just by changing a property. hence the name: implicitanimation.

the important part to take away is that when a property of a stand-alone layer changes, an animation object is added to the layer automatically.

getting back to updating the model and adding an explicit animation. in the case of the backing layer, the property change doesn’t cause an animation so it doesn’t matter if the layer is updated before or after adding the explicit animation. there is only going to be one animation anyway.

for the stand alone layer however, the property change causes and implicit animation to be added and we are adding an explicit animation so the layer ends up with two animations at the same time! this leads us to the next very interesting and deep topic: multiple animations.

it’s no surprise that different properties can be animated at the same time but multiple animations for the same property‽ surely one must cancel out the other. well, that is actually not the case. an animation is only canceled if another animation is added for the same key. so both animations are added to the layer, in order, and run until they are finished. it’s easy to verify by becoming the delegate of both the implicit and explicit animation and logging the “flag” in the <code>animationdidstop:finished:</code> callback. another, more visual way to see that both animations are running is to see how the animation looks in the two cases. if the model is updated first, then only the explicit animation can be seen. however, if the model is updated last so that the implicit animation added last, then the implicit animation can be seen running until completion and then the layer continues with the explicit animation as if it was running all along.

CALayer的additive屬性解析Multiple animationsAdditive animations

the two different animations when updating the model before and after adding the explicit animation.

there are two things to note in the above figure:

when the implicit animation completes, the layer doesn’t skip back to the beginning of the explicit animation.

in both cases the explicit animation end after the same amount of time.

to better understand what is happening it is best to look at how the animation is rendered.

disclaimer: i don’t work at apple and i haven’t seen the source code for core animation. what i’m about to explained is based on information available in documentation, wwdc videos, or information based on observations and experimentation.

for each frame the render server calculates the intermediate values for the animated properties for the specific time of that frame and applies them to the layers in the render tree.

knowing this, our two cases with the order of the implicit and explicit animations can be explained and visualized as follows.

in both cases the model value is changed to its new value at the start of the animation. it doesn’t matter if it happens before or after the explicit animation, the model value is updated in the copy of the layer tree that gets sent to the render server.

for the case where the model is updated first, the implicit animation is added to the layer first and the explicit animation is added after that. since the implicit animation is much shorter than the explicit animation, the intermediate values ii applies to the render tree is always overwritten by the intermediate values of the explicit animation. after a short while, the implicit animation finishes and is removed and the explicit animation runs alone until it finishes, applying its intermediate values directly over the model values. when the explicit animation finishes, it gets removed and we are left seeing the model values (which matches the end value of the explicit animation). the result looks like a smooth animation from the old value to the new value and the implicit animation is never seen.

note: there may be implementation details and optimizations that detect that the property is being overwritten by another animation and does something smart about it, but this is how we can think of the work being done by the render server.

CALayer的additive屬性解析Multiple animationsAdditive animations

for the case where the model is updated last, the implicit animation is added after the explicit animation. this means that in the beginning of the animation, the explicit animation sets its intermediate values and the implicit animation overwrites it with its own intermediate values. when the implicit animation finishes and is removed, the explicit animation is still just in the beginning, so for the next frame the property will be perceived as returning to an earlier value but it is actually showing the correct value for the explicit animation for that time. just as before, when it’s only the explicit animation left, it runs until completion at which point we are left seeing the model values. this animation doesn’t look right at all.

CALayer的additive屬性解析Multiple animationsAdditive animations

a breakdown of how the render server applies the implicit and explicit animations when the model is updated last

so, which of the two is it best practice to update the model value?

it’s actually a trick question because the answer it both neither place and either place. the best practice is actually to do the same thing as uiview is doing and disable the implicit animation. if we do that, then it doesn’t matter it we update the model before or after. it’s just personal preference. that said: i still like to update the model first:

i still prefer to use a transaction because i think that it’s clearer what it does. the task is to disable the implicit animation and the transaction does that directly by disabling the actions. to me, that is pretty much self documenting code.

it can seem odd to allow multiple animations for the same key path when it results in either strange animations or calculated values that are never seen, but it is actually what enables one of the more flexible and powerful features of caanimations: “additive” animations. instead of overwriting the value in the render tree, an additive animation adds to the value. it wouldn’t make sense to configure this one animation to be additive and change the model value at the same time. the model would update to its new value right away and the additive animation would add to that:

CALayer的additive屬性解析Multiple animationsAdditive animations

an illustration of what would happen if the explicit animation from before was made additive

instead we would use a regular animation to perform the transition between the old and new values and then use additive animations to make changes to the animation as it is happening. this can be used to create very dynamic animations and even add to an ongoing animation. for example, to make a small deviation from the straight path between two points:

CALayer的additive屬性解析Multiple animationsAdditive animations

an illustration of how an additive animation adds to another animation

this little trick can be used to acknowledge a users touch during an ongoing animation since it can be added to the running animation at any point.

multiple additive animations can also be used together to create really complex animations that would be very difficult to with just a single animation:

CALayer的additive屬性解析Multiple animationsAdditive animations

a complex animation created using multiple additive animations

for example, two keyframe animations with different paths can be combined to create an animation where one path loops around the other path, in this case a circle that loops around a heart:

CALayer的additive屬性解析Multiple animationsAdditive animations

a repeating animation of one path (a circle) looping while following another path (a heart)

there really isn’t much code to such a complex animation but you can imagine how hard it would be to generate a single path for the same type of animation.

for some inexplicable reason i completely missed session 236: “building interruptible and responsive interactions” (from wwdc 2014) when i first wrote this post. it also talks about additive animations and the role they play in ios 8 to create uikit animations that can seamlessly transition from one to the other. as mentioned in that video, the very same technique can be applied to any caanimation.

this works by updating the model value and creating additive animations that animate to zero from minus the difference between the new and old values. the result of one such animation looks just like the regular, smooth transition. when the animation starts out, the model value is updated by full change is subtracted meaning that the rendered value is back at its old value. as the animation progresses the subtracted value becomes smaller and smaller until it becomes zero when the animation finishes and the rendered value is the new model value.

what makes this so powerful is that if the model value changes during the animation, the first animation can continue until it’s contribution becomes zero while a new, similarly constructed additive animation is added on top of it. during the time that both animations are running, the contribution from the first animation becomes smaller and smaller while the contribution from the second animation becomes larger and larger. this makes it look like the animation is adjusting it’s target on the fly while maintaing momentum, taking some time to fully change its course.

CALayer的additive屬性解析Multiple animationsAdditive animations

a breakdown of how additive animations can be used to seamlessly transition from one animation to the other

the result is pretty astonishing and a great example of the kind of interaction that can be created using additive animations.

note: the animations in the above illustration (and all the other illustrations) are linear. visualizing easing would have made the illustrations more complex, but without easing there will be a noticeable change in velocity when transitioning between animations. the animations below uses easing to show what the real end result looks like.

CALayer的additive屬性解析Multiple animationsAdditive animations

the difference between regular and additive animations for transitioning between animations.

perhaps what’s most impressive is how little code it takes. we calculate the difference between the old and new value and use the negative difference as the from values (in this case i calculated the negative difference directly by subtracting the new value from the old value) and the use “zero” as the to value.

additive animations isn’t used all that often but can be a great tool both for rich interaction and complex animations. the new playgrounds in xcode 6 is a great way of experimenting with additive animations. until xcplayground becomes available for ios, you can create an os x playground and use<code>xcpshowview()</code> to display a live preview of the animating view. note that views behave differently on ios and os x, but stand alone layers work the same.

just because i couldn’t resists doing so, this is a visualization of what happens when an animation isn’t removed upon completion. that is one reason to avoid <code>removedoncompletion</code> in the common case but the main reason is still that it the model value no longer reflects what’t on screen, something that can lead to many strange bugs:

CALayer的additive屬性解析Multiple animationsAdditive animations

繼續閱讀