TransitionDrawable.java revision b3c56086d802ae28888dd97ba1f49bd6cee0b673
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.content.res.Resources.Theme;
21import android.graphics.Canvas;
22import 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 */
42public 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     */
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     */
88    TransitionDrawable() {
89        this(new TransitionState(null, null, null), null, null);
90    }
91
92    private TransitionDrawable(TransitionState state, Resources res, Theme theme) {
93        super(state, res, theme);
94    }
95
96    private TransitionDrawable(TransitionState state, Drawable[] layers) {
97        super(layers, state);
98    }
99
100    @Override
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     */
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     */
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     */
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
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     */
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     */
243    public boolean isCrossFadeEnabled() {
244        return mCrossFade;
245    }
246
247    static class TransitionState extends LayerState {
248        TransitionState(TransitionState orig, TransitionDrawable owner,
249                Resources res) {
250            super(orig, owner, res);
251        }
252
253        @Override
254        public Drawable newDrawable() {
255            return new TransitionDrawable(this, null, null);
256        }
257
258        @Override
259        public Drawable newDrawable(Resources res) {
260            return new TransitionDrawable(this, res, null);
261        }
262
263        @Override
264        public Drawable newDrawable(Resources res, Theme theme) {
265            return new TransitionDrawable(this, res, theme);
266        }
267
268        @Override
269        public int getChangingConfigurations() {
270            return mChangingConfigurations;
271        }
272    }
273}
274