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