1 /*
2  * Copyright (C) 2010 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 
17 package com.android.launcher3;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.animation.ValueAnimator;
23 import android.util.Property;
24 import android.view.View;
25 
26 import com.android.launcher3.util.Thunk;
27 
28 /**
29  * A convenience class for two-way animations, e.g. a fadeIn/fadeOut animation.
30  * With a regular ValueAnimator, if you call reverse to show the 'out' animation, you'll get
31  * a frame-by-frame mirror of the 'in' animation -- i.e., the interpolated values will
32  * be exactly reversed. Using this class, both the 'in' and the 'out' animation use the
33  * interpolator in the same direction.
34  */
35 public class InterruptibleInOutAnimator {
36 
37     private static final Property<InterruptibleInOutAnimator, Float> VALUE =
38             new Property<InterruptibleInOutAnimator, Float>(Float.TYPE, "value") {
39                 @Override
40                 public Float get(InterruptibleInOutAnimator anim) {
41                     return anim.mValue;
42                 }
43 
44                 @Override
45                 public void set(InterruptibleInOutAnimator anim, Float value) {
46                     anim.mValue = value;
47                 }
48             };
49 
50     private long mOriginalDuration;
51     private float mOriginalFromValue;
52     private float mOriginalToValue;
53     private ValueAnimator mAnimator;
54 
55     private float mValue;
56 
57     private boolean mFirstRun = true;
58 
59     private Object mTag = null;
60 
61     private static final int STOPPED = 0;
62     private static final int IN = 1;
63     private static final int OUT = 2;
64 
65     // TODO: This isn't really necessary, but is here to help diagnose a bug in the drag viz
66     @Thunk int mDirection = STOPPED;
67 
InterruptibleInOutAnimator(long duration, float fromValue, float toValue)68     public InterruptibleInOutAnimator(long duration, float fromValue, float toValue) {
69         mAnimator = ObjectAnimator.ofFloat(this, VALUE, fromValue, toValue).setDuration(duration);
70         mOriginalDuration = duration;
71         mOriginalFromValue = fromValue;
72         mOriginalToValue = toValue;
73 
74         mAnimator.addListener(new AnimatorListenerAdapter() {
75             @Override
76             public void onAnimationEnd(Animator animation) {
77                 mDirection = STOPPED;
78             }
79         });
80     }
81 
animate(int direction)82     private void animate(int direction) {
83         final long currentPlayTime = mAnimator.getCurrentPlayTime();
84         final float toValue = (direction == IN) ? mOriginalToValue : mOriginalFromValue;
85         final float startValue = mFirstRun ? mOriginalFromValue : mValue;
86 
87         // Make sure it's stopped before we modify any values
88         cancel();
89 
90         // TODO: We don't really need to do the animation if startValue == toValue, but
91         // somehow that doesn't seem to work, possibly a quirk of the animation framework
92         mDirection = direction;
93 
94         // Ensure we don't calculate a non-sensical duration
95         long duration = mOriginalDuration - currentPlayTime;
96         mAnimator.setDuration(Math.max(0, Math.min(duration, mOriginalDuration)));
97 
98         mAnimator.setFloatValues(startValue, toValue);
99         mAnimator.start();
100         mFirstRun = false;
101     }
102 
cancel()103     public void cancel() {
104         mAnimator.cancel();
105         mDirection = STOPPED;
106     }
107 
end()108     public void end() {
109         mAnimator.end();
110         mDirection = STOPPED;
111     }
112 
113     /**
114      * Return true when the animation is not running and it hasn't even been started.
115      */
isStopped()116     public boolean isStopped() {
117         return mDirection == STOPPED;
118     }
119 
120     /**
121      * This is the equivalent of calling Animator.start(), except that it can be called when
122      * the animation is running in the opposite direction, in which case we reverse
123      * direction and animate for a correspondingly shorter duration.
124      */
animateIn()125     public void animateIn() {
126         animate(IN);
127     }
128 
129     /**
130      * This is the roughly the equivalent of calling Animator.reverse(), except that it uses the
131      * same interpolation curve as animateIn(), rather than mirroring it. Also, like animateIn(),
132      * if the animation is currently running in the opposite direction, we reverse
133      * direction and animate for a correspondingly shorter duration.
134      */
animateOut()135     public void animateOut() {
136         animate(OUT);
137     }
138 
setTag(Object tag)139     public void setTag(Object tag) {
140         mTag = tag;
141     }
142 
getTag()143     public Object getTag() {
144         return mTag;
145     }
146 
getAnimator()147     public ValueAnimator getAnimator() {
148         return mAnimator;
149     }
150 }
151