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