1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.anim;
17 
18 import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur;
19 
20 import android.animation.Animator;
21 import android.animation.Animator.AnimatorListener;
22 import android.animation.AnimatorSet;
23 import android.animation.ObjectAnimator;
24 import android.animation.TimeInterpolator;
25 import android.animation.ValueAnimator;
26 import android.util.FloatProperty;
27 import android.util.IntProperty;
28 import android.view.View;
29 
30 import com.android.launcher3.anim.AnimatorPlaybackController.Holder;
31 
32 import java.util.ArrayList;
33 import java.util.function.Consumer;
34 
35 /**
36  * Utility class to keep track of a running animation.
37  *
38  * This class allows attaching end callbacks to an animation is intended to be used with
39  * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
40  * AnimationListeners are not properly dispatched.
41  *
42  * TODO: Find a better name
43  */
44 public class PendingAnimation implements PropertySetter {
45 
46     private final ArrayList<Consumer<EndState>> mEndListeners = new ArrayList<>();
47 
48     private final ArrayList<Holder> mAnimHolders = new ArrayList<>();
49     private final AnimatorSet mAnim;
50     private final long mDuration;
51 
52     private ValueAnimator mProgressAnimator;
53 
PendingAnimation(long duration)54     public PendingAnimation(long  duration) {
55         mDuration = duration;
56         mAnim = new AnimatorSet();
57     }
58 
59     /**
60      * Utility method to sent an interpolator on an animation and add it to the list
61      */
add(Animator anim, TimeInterpolator interpolator, SpringProperty springProperty)62     public void add(Animator anim, TimeInterpolator interpolator, SpringProperty springProperty) {
63         anim.setInterpolator(interpolator);
64         add(anim, springProperty);
65     }
66 
add(Animator anim)67     public void add(Animator anim) {
68         add(anim, SpringProperty.DEFAULT);
69     }
70 
add(Animator a, SpringProperty springProperty)71     public void add(Animator a, SpringProperty springProperty) {
72         mAnim.play(a.setDuration(mDuration));
73         addAnimationHoldersRecur(a, mDuration, springProperty, mAnimHolders);
74     }
75 
finish(boolean isSuccess, int logAction)76     public void finish(boolean isSuccess, int logAction) {
77         for (Consumer<EndState> listeners : mEndListeners) {
78             listeners.accept(new EndState(isSuccess, logAction));
79         }
80         mEndListeners.clear();
81     }
82 
83     @Override
setViewAlpha(View view, float alpha, TimeInterpolator interpolator)84     public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
85         if (view == null || view.getAlpha() == alpha) {
86             return;
87         }
88         ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
89         anim.addListener(new AlphaUpdateListener(view));
90         anim.setInterpolator(interpolator);
91         add(anim);
92     }
93 
94     @Override
setFloat(T target, FloatProperty<T> property, float value, TimeInterpolator interpolator)95     public <T> void setFloat(T target, FloatProperty<T> property, float value,
96             TimeInterpolator interpolator) {
97         if (property.get(target) == value) {
98             return;
99         }
100         Animator anim = ObjectAnimator.ofFloat(target, property, value);
101         anim.setDuration(mDuration).setInterpolator(interpolator);
102         add(anim);
103     }
104 
addFloat(T target, FloatProperty<T> property, float from, float to, TimeInterpolator interpolator)105     public <T> void addFloat(T target, FloatProperty<T> property, float from, float to,
106             TimeInterpolator interpolator) {
107         Animator anim = ObjectAnimator.ofFloat(target, property, from, to);
108         anim.setInterpolator(interpolator);
109         add(anim);
110     }
111 
112     @Override
setInt(T target, IntProperty<T> property, int value, TimeInterpolator interpolator)113     public <T> void setInt(T target, IntProperty<T> property, int value,
114             TimeInterpolator interpolator) {
115         if (property.get(target) == value) {
116             return;
117         }
118         Animator anim = ObjectAnimator.ofInt(target, property, value);
119         anim.setInterpolator(interpolator);
120         add(anim);
121     }
122 
123     /**
124      * Adds a callback to be run on every frame of the animation
125      */
addOnFrameCallback(Runnable runnable)126     public void addOnFrameCallback(Runnable runnable) {
127         if (mProgressAnimator == null) {
128             mProgressAnimator = ValueAnimator.ofFloat(0, 1);
129         }
130 
131         mProgressAnimator.addUpdateListener(anim -> runnable.run());
132     }
133 
134     /**
135      * @see AnimatorSet#addListener(AnimatorListener)
136      */
addListener(Animator.AnimatorListener listener)137     public void addListener(Animator.AnimatorListener listener) {
138         mAnim.addListener(listener);
139     }
140 
141     /**
142      * Creates and returns the underlying AnimatorSet
143      */
buildAnim()144     public AnimatorSet buildAnim() {
145         // Add progress animation to the end, so that frame callback is called after all the other
146         // animation update.
147         if (mProgressAnimator != null) {
148             add(mProgressAnimator);
149             mProgressAnimator = null;
150         }
151         if (mAnimHolders.isEmpty()) {
152             // Add a dummy animation to that the duration is respected
153             add(ValueAnimator.ofFloat(0, 1).setDuration(mDuration));
154         }
155         return mAnim;
156     }
157 
158     /**
159      * Creates a controller for this animation
160      */
createPlaybackController()161     public AnimatorPlaybackController createPlaybackController() {
162         return new AnimatorPlaybackController(buildAnim(), mDuration, mAnimHolders);
163     }
164 
165     /**
166      * Add a listener of receiving the end state.
167      * Note that the listeners are called as a result of calling {@link #finish(boolean, int)}
168      * and not automatically
169      */
addEndListener(Consumer<EndState> listener)170     public void addEndListener(Consumer<EndState> listener) {
171         mEndListeners.add(listener);
172     }
173 
174     public static class EndState {
175         public boolean isSuccess;
176         public int logAction;
177 
EndState(boolean isSuccess, int logAction)178         public EndState(boolean isSuccess, int logAction) {
179             this.isSuccess = isSuccess;
180             this.logAction = logAction;
181         }
182     }
183 }
184