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