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