1 /*
2  * Copyright (C) 2008 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 android.graphics.drawable;
18 
19 import android.content.pm.ActivityInfo.Config;
20 import android.content.res.Resources;
21 import android.graphics.Canvas;
22 import android.os.SystemClock;
23 
24 /**
25  * An extension of LayerDrawables that is intended to cross-fade between
26  * the first and second layer. To start the transition, call {@link #startTransition(int)}. To
27  * display just the first layer, call {@link #resetTransition()}.
28  * <p>
29  * It can be defined in an XML file with the <code>&lt;transition></code> element.
30  * Each Drawable in the transition is defined in a nested <code>&lt;item></code>. For more
31  * information, see the guide to <a
32  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
33  *
34  * @attr ref android.R.styleable#LayerDrawableItem_left
35  * @attr ref android.R.styleable#LayerDrawableItem_top
36  * @attr ref android.R.styleable#LayerDrawableItem_right
37  * @attr ref android.R.styleable#LayerDrawableItem_bottom
38  * @attr ref android.R.styleable#LayerDrawableItem_drawable
39  * @attr ref android.R.styleable#LayerDrawableItem_id
40  *
41  */
42 public class TransitionDrawable extends LayerDrawable implements Drawable.Callback {
43 
44     /**
45      * A transition is about to start.
46      */
47     private static final int TRANSITION_STARTING = 0;
48 
49     /**
50      * The transition has started and the animation is in progress
51      */
52     private static final int TRANSITION_RUNNING = 1;
53 
54     /**
55      * No transition will be applied
56      */
57     private static final int TRANSITION_NONE = 2;
58 
59     /**
60      * The current state of the transition. One of {@link #TRANSITION_STARTING},
61      * {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE}
62      */
63     private int mTransitionState = TRANSITION_NONE;
64 
65     private boolean mReverse;
66     private long mStartTimeMillis;
67     private int mFrom;
68     private int mTo;
69     private int mDuration;
70     private int mOriginalDuration;
71     private int mAlpha = 0;
72     private boolean mCrossFade;
73 
74     /**
75      * Create a new transition drawable with the specified list of layers. At least
76      * 2 layers are required for this drawable to work properly.
77      */
TransitionDrawable(Drawable[] layers)78     public TransitionDrawable(Drawable[] layers) {
79         this(new TransitionState(null, null, null), layers);
80     }
81 
82     /**
83      * Create a new transition drawable with no layer. To work correctly, at least 2
84      * layers must be added to this drawable.
85      *
86      * @see #TransitionDrawable(Drawable[])
87      */
TransitionDrawable()88     TransitionDrawable() {
89         this(new TransitionState(null, null, null), (Resources) null);
90     }
91 
TransitionDrawable(TransitionState state, Resources res)92     private TransitionDrawable(TransitionState state, Resources res) {
93         super(state, res);
94     }
95 
TransitionDrawable(TransitionState state, Drawable[] layers)96     private TransitionDrawable(TransitionState state, Drawable[] layers) {
97         super(layers, state);
98     }
99 
100     @Override
createConstantState(LayerState state, Resources res)101     LayerState createConstantState(LayerState state, Resources res) {
102         return new TransitionState((TransitionState) state, this, res);
103     }
104 
105     /**
106      * Begin the second layer on top of the first layer.
107      *
108      * @param durationMillis The length of the transition in milliseconds
109      */
startTransition(int durationMillis)110     public void startTransition(int durationMillis) {
111         mFrom = 0;
112         mTo = 255;
113         mAlpha = 0;
114         mDuration = mOriginalDuration = durationMillis;
115         mReverse = false;
116         mTransitionState = TRANSITION_STARTING;
117         invalidateSelf();
118     }
119 
120     /**
121      * Show only the first layer.
122      */
resetTransition()123     public void resetTransition() {
124         mAlpha = 0;
125         mTransitionState = TRANSITION_NONE;
126         invalidateSelf();
127     }
128 
129     /**
130      * Reverses the transition, picking up where the transition currently is.
131      * If the transition is not currently running, this will start the transition
132      * with the specified duration. If the transition is already running, the last
133      * known duration will be used.
134      *
135      * @param duration The duration to use if no transition is running.
136      */
reverseTransition(int duration)137     public void reverseTransition(int duration) {
138         final long time = SystemClock.uptimeMillis();
139         // Animation is over
140         if (time - mStartTimeMillis > mDuration) {
141             if (mTo == 0) {
142                 mFrom = 0;
143                 mTo = 255;
144                 mAlpha = 0;
145                 mReverse = false;
146             } else {
147                 mFrom = 255;
148                 mTo = 0;
149                 mAlpha = 255;
150                 mReverse = true;
151             }
152             mDuration = mOriginalDuration = duration;
153             mTransitionState = TRANSITION_STARTING;
154             invalidateSelf();
155             return;
156         }
157 
158         mReverse = !mReverse;
159         mFrom = mAlpha;
160         mTo = mReverse ? 0 : 255;
161         mDuration = (int) (mReverse ? time - mStartTimeMillis :
162                 mOriginalDuration - (time - mStartTimeMillis));
163         mTransitionState = TRANSITION_STARTING;
164     }
165 
166     @Override
draw(Canvas canvas)167     public void draw(Canvas canvas) {
168         boolean done = true;
169 
170         switch (mTransitionState) {
171             case TRANSITION_STARTING:
172                 mStartTimeMillis = SystemClock.uptimeMillis();
173                 done = false;
174                 mTransitionState = TRANSITION_RUNNING;
175                 break;
176 
177             case TRANSITION_RUNNING:
178                 if (mStartTimeMillis >= 0) {
179                     float normalized = (float)
180                             (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration;
181                     done = normalized >= 1.0f;
182                     normalized = Math.min(normalized, 1.0f);
183                     mAlpha = (int) (mFrom  + (mTo - mFrom) * normalized);
184                 }
185                 break;
186         }
187 
188         final int alpha = mAlpha;
189         final boolean crossFade = mCrossFade;
190         final ChildDrawable[] array = mLayerState.mChildren;
191 
192         if (done) {
193             // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw
194             // the appropriate drawable[s] and return
195             if (!crossFade || alpha == 0) {
196                 array[0].mDrawable.draw(canvas);
197             }
198             if (alpha == 0xFF) {
199                 array[1].mDrawable.draw(canvas);
200             }
201             return;
202         }
203 
204         Drawable d;
205         d = array[0].mDrawable;
206         if (crossFade) {
207             d.setAlpha(255 - alpha);
208         }
209         d.draw(canvas);
210         if (crossFade) {
211             d.setAlpha(0xFF);
212         }
213 
214         if (alpha > 0) {
215             d = array[1].mDrawable;
216             d.setAlpha(alpha);
217             d.draw(canvas);
218             d.setAlpha(0xFF);
219         }
220 
221         if (!done) {
222             invalidateSelf();
223         }
224     }
225 
226     /**
227      * Enables or disables the cross fade of the drawables. When cross fade
228      * is disabled, the first drawable is always drawn opaque. With cross
229      * fade enabled, the first drawable is drawn with the opposite alpha of
230      * the second drawable. Cross fade is disabled by default.
231      *
232      * @param enabled True to enable cross fading, false otherwise.
233      */
setCrossFadeEnabled(boolean enabled)234     public void setCrossFadeEnabled(boolean enabled) {
235         mCrossFade = enabled;
236     }
237 
238     /**
239      * Indicates whether the cross fade is enabled for this transition.
240      *
241      * @return True if cross fading is enabled, false otherwise.
242      */
isCrossFadeEnabled()243     public boolean isCrossFadeEnabled() {
244         return mCrossFade;
245     }
246 
247     static class TransitionState extends LayerState {
TransitionState(TransitionState orig, TransitionDrawable owner, Resources res)248         TransitionState(TransitionState orig, TransitionDrawable owner, Resources res) {
249             super(orig, owner, res);
250         }
251 
252         @Override
newDrawable()253         public Drawable newDrawable() {
254             return new TransitionDrawable(this, (Resources) null);
255         }
256 
257         @Override
newDrawable(Resources res)258         public Drawable newDrawable(Resources res) {
259             return new TransitionDrawable(this, res);
260         }
261 
262         @Override
getChangingConfigurations()263         public @Config int getChangingConfigurations() {
264             return mChangingConfigurations;
265         }
266     }
267 }
268