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 (mAlpha == 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        Drawable d;
191
192        d = array[0].mDrawable;
193        if (crossFade) {
194            d.setAlpha(255 - alpha);
195        }
196        d.draw(canvas);
197        if (crossFade) {
198            d.setAlpha(0xFF);
199        }
200
201        if (alpha > 0) {
202            d = array[1].mDrawable;
203            d.setAlpha(alpha);
204            d.draw(canvas);
205            d.setAlpha(0xFF);
206        }
207
208        if (!done) {
209            invalidateSelf();
210        }
211    }
212
213    /**
214     * Enables or disables the cross fade of the drawables. When cross fade
215     * is disabled, the first drawable is always drawn opaque. With cross
216     * fade enabled, the first drawable is drawn with the opposite alpha of
217     * the second drawable. Cross fade is disabled by default.
218     *
219     * @param enabled True to enable cross fading, false otherwise.
220     */
221    public void setCrossFadeEnabled(boolean enabled) {
222        mCrossFade = enabled;
223    }
224
225    /**
226     * Indicates whether the cross fade is enabled for this transition.
227     *
228     * @return True if cross fading is enabled, false otherwise.
229     */
230    public boolean isCrossFadeEnabled() {
231        return mCrossFade;
232    }
233
234    static class TransitionState extends LayerState {
235        TransitionState(TransitionState orig, TransitionDrawable owner,
236                Resources res) {
237            super(orig, owner, res);
238        }
239
240        @Override
241        public Drawable newDrawable() {
242            return new TransitionDrawable(this, (Resources)null);
243        }
244
245        @Override
246        public Drawable newDrawable(Resources res) {
247            return new TransitionDrawable(this, res);
248        }
249
250        @Override
251        public int getChangingConfigurations() {
252            return mChangingConfigurations;
253        }
254    }
255}
256