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