Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 1 | |
| 2 | ## Layer Animation Builder |
| 3 | |
| 4 | |
| 5 | ### Purpose |
| 6 | |
| 7 | Integrating animation and kinetic UI movements can be difficult to set up. Where to start and what to override (if anything) isn’t very clear. Making animations more declarative in nature rather than manually gluing all the lower-level animation subsystems together will simplify their use. |
| 8 | |
| 9 | This system provides a programmatic model that is similar to how the UX team creates animations. This ensures more fidelity between the intent of the UX designer and the resulting code. The visual similarities between the presentation used by the UX and the actual code constructing those sequences will make maintenance and updates easier and quicker. |
| 10 | |
| 11 | |
| 12 | ### Example |
| 13 | |
| 14 | In order to better demonstrate how this system makes constructing animations easier an example should make this more clear. |
| 15 | |
| 16 | The following code will create a repeating cross-fading animation shown below. |
| 17 | |
| 18 |    |
| 19 | |
James Cook | aec0504 | 2021-10-28 17:53:08 | [diff] [blame] | 20 | You can see this animation by running `views_examples` on Windows or Linux: |
| 21 | |
| 22 | ``` shell |
| 23 | $ autoninja -C out\Default views_examples |
| 24 | $ out\Default\views_examples --enable-examples="Fade Animation" |
| 25 | ``` |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 26 | |
| 27 | Traditionally this would be how the above animation would be created. |
| 28 | |
| 29 | |
| 30 | ``` cpp |
| 31 | auto primary_sequence = std::make_unique<ui::LayerAnimationSequence>(); |
| 32 | auto secondary_sequence = std::make_unique<ui::LayerAnimationSequence>(); |
| 33 | primary_sequence->set_is_repeating(true); |
| 34 | secondary_sequence->set_is_repeating(true); |
| 35 | |
| 36 | primary_sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement( |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 37 | ui::LayerAnimationElement::OPACITY, base::Seconds(2))); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 38 | primary_sequence->AddElement(ui::LayerAnimationElement::CreateOpacityElement( |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 39 | 0.0f, base::Seconds(1))); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 40 | primary_sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement( |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 41 | ui::LayerAnimationElement::OPACITY, base::Seconds(2))); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 42 | primary_sequence->AddElement(ui::LayerAnimationElement::CreateOpacityElement( |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 43 | 1.0f, base::Seconds(1))); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 44 | |
| 45 | secondary_sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement( |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 46 | ui::LayerAnimationElement::OPACITY, base::Seconds(2))); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 47 | secondary_sequence->AddElement( |
| 48 | ui::LayerAnimationElement::CreateOpacityElement( |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 49 | 1.0f, base::Seconds(1))); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 50 | secondary_sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement( |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 51 | ui::LayerAnimationElement::OPACITY, base::Seconds(2))); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 52 | secondary_sequence->AddElement( |
| 53 | ui::LayerAnimationElement::CreateOpacityElement( |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 54 | 0.0f, base::Seconds(1))); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 55 | |
| 56 | primary_view_->layer()->GetAnimator()->StartAnimation( |
| 57 | primary_sequence.release()); |
| 58 | secondary_view_->layer()->GetAnimator()->StartAnimation( |
| 59 | secondary_sequence.release()); |
| 60 | ``` |
| 61 | |
| 62 | |
| 63 | Using the new builder system described in this document, the above code could be done much simpler. |
| 64 | |
| 65 | |
| 66 | ``` cpp |
| 67 | AnimationBuilder() |
| 68 | .Repeatedly() |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 69 | .Offset(base::Seconds(2)) |
| 70 | .SetDuration(base::Seconds(1)) |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 71 | .SetOpacity(primary_view_, 0.0f) |
| 72 | .SetOpacity(secondary_view_, 1.0f) |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 73 | .Offset(base::Seconds(2)) |
| 74 | .SetDuration(base::Seconds(1)) |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 75 | .SetOpacity(primary_view_, 1.0f) |
| 76 | .SetOpacity(secondary_view_, 0.0f); |
| 77 | ``` |
| 78 | |
| 79 | |
| 80 | The AnimationBuilder will handle proper insertion of pause elements into the timeline, so no specific need to insert them. It will also handle coordination of the animations across multiple [LayerOwner](https://source.chromium.org/chromium/chromium/src/+/main:ui/compositor/layer_owner.h;l=19)s or across specific [Layers](https://source.chromium.org/chromium/chromium/src/+/main:ui/compositor/layer.h;drc=af5bb21d9ee6585e4111fbc9089d1f5c7edde034;l=69). |
| 81 | |
| 82 | It should be obvious that the above code is both shorter and more immediately clear what the intent of the code is doing along with how the animations interact across the two views. |
| 83 | |
| 84 | |
| 85 | ### API Reference |
| 86 | |
Elaine Chien | 46fb509 | 2021-09-15 21:55:03 | [diff] [blame] | 87 | Currently AnimationBuilder operates only on a LayerOwner or directly on a Layer. Since views::View is also a LayerOwner, it will also operate directly on Views. To animate a View, the View must first paint to it’s layer by calling View::SetPaintToLayer. |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 88 | |
Elaine Chien | 46fb509 | 2021-09-15 21:55:03 | [diff] [blame] | 89 | The AnimationBuilder is a scoped object that starts the animations after the AnimationBuilder goes out of scope. This can be done as a single statement (as shown above) or across multiple statements with local temps. |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 90 | |
| 91 | NOTE: It is generally _not_ recommended to hold onto an AnimationBuilder instance beyond the current scope or store it in instance variables. This could lead to UAFs or other undesirable behaviors. Simply construct the animation at the point at which it is needed. |
| 92 | |
| 93 | To use AnimationBuilder include the following header: |
| 94 | |
| 95 | |
| 96 | ``` cpp |
| 97 | #include "ui/views/animation/animation_builder.h" |
| 98 | ``` |
| 99 | |
Elaine Chien | 46fb509 | 2021-09-15 21:55:03 | [diff] [blame] | 100 | The AnimationBuilder consists of the main object along with an inner AnimationSequenceBlock. |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 101 | |
Elaine Chien | 46fb509 | 2021-09-15 21:55:03 | [diff] [blame] | 102 | An AnimationSequenceBlock is a single unit of a larger animation sequence, which has a start time, duration, and zero or more (target, property) animations. |
| 103 | * There may be multiple properties animating on a single target, and/or multiple targets animating, but the same property on the same target may only be animated at most once per block. Animations can be added by calling SetXXX(). |
| 104 | * Calling At(), Offset(), or Then() creates a new block. All animations in the same AnimationSequenceBlock will run in parallel. Create multiple AnimationSequenceBlocks to run animations in sequence. |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 105 | |
Elaine Chien | 46fb509 | 2021-09-15 21:55:03 | [diff] [blame] | 106 | Calls on AnimationBuilder affect the whole animations and calls on AnimationSequenceBlock affect only that particular block. |
| 107 | |
Elaine Chien | 9ee33317 | 2022-07-28 13:04:31 | [diff] [blame] | 108 | #### Setting callbacks |
| 109 | When setting callbacks for the animations note that the AnimationBuilder’s observer that calls these callbacks may outlive the callback's parameters. |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 110 | |
Elaine Chien | eb7088a | 2022-08-02 00:15:54 | [diff] [blame] | 111 | The OnEnded callback runs when all animations created on the AnimationBuilder have finished. The OnAborted callback runs when any one animation created on the AnimationBuilder has been aborted. Therefore, these callbacks and every object the callback accesses needs to outlive all the Layers/LayerOwners being animated on since the Layers ultimately own the objects that run the animation. Otherwise developers may need to use weak pointers or force animations to be cancelled in the object’s destructor to prevent accessing destroyed objects. Note that aborted notifications can be sent during the destruction process. Therefore subclasses that own the Layers may actually be destroyed before the OnAborted callback is run. |
Elaine Chien | 9ee33317 | 2022-07-28 13:04:31 | [diff] [blame] | 112 | |
| 113 | #### API |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 114 | ``` cpp |
| 115 | class VIEWS_EXPORT AnimationBuilder { |
| 116 | public: |
| 117 | AnimationBuilder(); |
| 118 | ~AnimationBuilder(); |
| 119 | |
| 120 | // Options for the whole animation |
Elaine Chien | 46fb509 | 2021-09-15 21:55:03 | [diff] [blame] | 121 | |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 122 | AnimationBuilder& SetPreemptionStrategy( |
| 123 | ui::LayerAnimator::PreemptionStrategy preemption_strategy); |
Elaine Chien | 46fb509 | 2021-09-15 21:55:03 | [diff] [blame] | 124 | // Called when the animation starts. |
| 125 | AnimationBuilder& OnStarted(base::OnceClosure callback); |
| 126 | // Called when the animation ends. Not called if animation is aborted. |
| 127 | AnimationBuilder& OnEnded(base::OnceClosure callback); |
| 128 | // Called when a sequence repetition ends and will repeat. Not called if |
| 129 | // sequence is aborted. |
| 130 | AnimationBuilder& OnWillRepeat(base::RepeatingClosure callback); |
| 131 | // Called if animation is aborted for any reason. Should never do anything |
| 132 | // that may cause another animation to be started. |
| 133 | AnimationBuilder& OnAborted(base::OnceClosure callback); |
| 134 | // Called when the animation is scheduled. |
| 135 | AnimationBuilder& OnScheduled(base::OnceClosure callback); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 136 | |
| 137 | // Creates a new sequence (that optionally repeats). |
| 138 | AnimationSequenceBlock Once(); |
| 139 | AnimationSequenceBlock Repeatedly(); |
| 140 | |
| 141 | // Returns a handle that can be destroyed later to abort all running |
| 142 | // animations. |
| 143 | // Caveat: ALL properties will be aborted, including those not initiated |
| 144 | // by the builder. |
| 145 | std::unique_ptr<AnimationAbortHandle> GetAbortHandle(); |
| 146 | }; |
| 147 | |
Elaine Chien | 46fb509 | 2021-09-15 21:55:03 | [diff] [blame] | 148 | |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 149 | class VIEWS_EXPORT AnimationSequenceBlock { |
| 150 | public: |
| 151 | AnimationSequenceBlock(base::PassKey<AnimationBuilder> builder_key, |
| 152 | AnimationBuilder* owner, |
| 153 | base::TimeDelta start); |
| 154 | AnimationSequenceBlock(AnimationSequenceBlock&& other); |
| 155 | AnimationSequenceBlock& operator=(AnimationSequenceBlock&& other); |
| 156 | ~AnimationSequenceBlock(); |
| 157 | |
| 158 | // Sets the duration of this block. The duration may be set at most once and |
| 159 | // will be zero if unspecified. |
| 160 | AnimationSequenceBlock& SetDuration(base::TimeDelta duration); |
| 161 | |
| 162 | // Adds animation elements to this block. Each (target, property) pair may be |
| 163 | // added at most once. |
Elaine Chien | 46fb509 | 2021-09-15 21:55:03 | [diff] [blame] | 164 | // These property setter methods can also take in a ui::Layer as the target. |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 165 | AnimationSequenceBlock& SetBounds( |
| 166 | ui::LayerOwner* target, |
| 167 | const gfx::Rect& bounds, |
| 168 | gfx::Tween::Type tween_type = gfx::Tween::LINEAR); |
| 169 | AnimationSequenceBlock& SetBrightness( |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 170 | ui::LayerOwner* target, |
| 171 | float brightness, |
| 172 | gfx::Tween::Type tween_type = gfx::Tween::LINEAR); |
| 173 | AnimationSequenceBlock& SetClipRect( |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 174 | ui::LayerOwner* target, |
| 175 | const gfx::Rect& clip_rect, |
| 176 | gfx::Tween::Type tween_type = gfx::Tween::LINEAR); |
| 177 | AnimationSequenceBlock& SetColor( |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 178 | ui::LayerOwner* target, |
| 179 | SkColor color, |
| 180 | gfx::Tween::Type tween_type = gfx::Tween::LINEAR); |
| 181 | AnimationSequenceBlock& SetGrayscale( |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 182 | ui::LayerOwner* target, |
| 183 | float grayscale, |
| 184 | gfx::Tween::Type tween_type = gfx::Tween::LINEAR); |
| 185 | AnimationSequenceBlock& SetOpacity( |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 186 | ui::LayerOwner* target, |
| 187 | float opacity, |
| 188 | gfx::Tween::Type tween_type = gfx::Tween::LINEAR); |
| 189 | AnimationSequenceBlock& SetTransform( |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 190 | ui::LayerOwner* target, |
| 191 | gfx::Transform transform, |
| 192 | gfx::Tween::Type tween_type = gfx::Tween::LINEAR); |
| 193 | AnimationSequenceBlock& SetRoundedCorners( |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 194 | ui::LayerOwner* target, |
| 195 | const gfx::RoundedCornersF& rounded_corners, |
| 196 | gfx::Tween::Type tween_type = gfx::Tween::LINEAR); |
| 197 | AnimationSequenceBlock& SetVisibility( |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 198 | ui::LayerOwner* target, |
| 199 | bool visible, |
| 200 | gfx::Tween::Type tween_type = gfx::Tween::LINEAR); |
| 201 | |
| 202 | // Creates a new block. |
| 203 | AnimationSequenceBlock At(base::TimeDelta since_sequence_start); |
| 204 | AnimationSequenceBlock Offset(base::TimeDelta since_last_block_start); |
| 205 | AnimationSequenceBlock Then(); |
Wei Li | fc6f3e0 | 2021-09-08 23:52:33 | [diff] [blame] | 206 | }; |
| 207 | ``` |
| 208 | |