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.content.pm.ActivityInfo.Config; 20 import android.content.res.Resources; 21 import android.graphics.Canvas; 22 import 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><transition></code> element. 30 * Each Drawable in the transition is defined in a nested <code><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 */ 42 public 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 */ TransitionDrawable(Drawable[] layers)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 */ TransitionDrawable()88 TransitionDrawable() { 89 this(new TransitionState(null, null, null), (Resources) null); 90 } 91 TransitionDrawable(TransitionState state, Resources res)92 private TransitionDrawable(TransitionState state, Resources res) { 93 super(state, res); 94 } 95 TransitionDrawable(TransitionState state, Drawable[] layers)96 private TransitionDrawable(TransitionState state, Drawable[] layers) { 97 super(layers, state); 98 } 99 100 @Override createConstantState(LayerState state, Resources res)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 */ startTransition(int durationMillis)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 the second layer on top of the first layer immediately 122 * 123 * @hide 124 */ showSecondLayer()125 public void showSecondLayer() { 126 mAlpha = 255; 127 mReverse = false; 128 mTransitionState = TRANSITION_NONE; 129 invalidateSelf(); 130 } 131 132 /** 133 * Show only the first layer. 134 */ resetTransition()135 public void resetTransition() { 136 mAlpha = 0; 137 mTransitionState = TRANSITION_NONE; 138 invalidateSelf(); 139 } 140 141 /** 142 * Reverses the transition, picking up where the transition currently is. 143 * If the transition is not currently running, this will start the transition 144 * with the specified duration. If the transition is already running, the last 145 * known duration will be used. 146 * 147 * @param duration The duration to use if no transition is running. 148 */ reverseTransition(int duration)149 public void reverseTransition(int duration) { 150 final long time = SystemClock.uptimeMillis(); 151 // Animation is over 152 if (time - mStartTimeMillis > mDuration) { 153 if (mTo == 0) { 154 mFrom = 0; 155 mTo = 255; 156 mAlpha = 0; 157 mReverse = false; 158 } else { 159 mFrom = 255; 160 mTo = 0; 161 mAlpha = 255; 162 mReverse = true; 163 } 164 mDuration = mOriginalDuration = duration; 165 mTransitionState = TRANSITION_STARTING; 166 invalidateSelf(); 167 return; 168 } 169 170 mReverse = !mReverse; 171 mFrom = mAlpha; 172 mTo = mReverse ? 0 : 255; 173 mDuration = (int) (mReverse ? time - mStartTimeMillis : 174 mOriginalDuration - (time - mStartTimeMillis)); 175 mTransitionState = TRANSITION_STARTING; 176 } 177 178 @Override draw(Canvas canvas)179 public void draw(Canvas canvas) { 180 boolean done = true; 181 182 switch (mTransitionState) { 183 case TRANSITION_STARTING: 184 mStartTimeMillis = SystemClock.uptimeMillis(); 185 done = false; 186 mTransitionState = TRANSITION_RUNNING; 187 break; 188 189 case TRANSITION_RUNNING: 190 if (mStartTimeMillis >= 0) { 191 float normalized = (float) 192 (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration; 193 done = normalized >= 1.0f; 194 normalized = Math.min(normalized, 1.0f); 195 mAlpha = (int) (mFrom + (mTo - mFrom) * normalized); 196 } 197 break; 198 } 199 200 final int alpha = mAlpha; 201 final boolean crossFade = mCrossFade; 202 final ChildDrawable[] array = mLayerState.mChildren; 203 204 if (done) { 205 // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw 206 // the appropriate drawable[s] and return 207 if (!crossFade || alpha == 0) { 208 array[0].mDrawable.draw(canvas); 209 } 210 if (alpha == 0xFF) { 211 array[1].mDrawable.draw(canvas); 212 } 213 return; 214 } 215 216 Drawable d; 217 d = array[0].mDrawable; 218 if (crossFade) { 219 d.setAlpha(255 - alpha); 220 } 221 d.draw(canvas); 222 if (crossFade) { 223 d.setAlpha(0xFF); 224 } 225 226 if (alpha > 0) { 227 d = array[1].mDrawable; 228 d.setAlpha(alpha); 229 d.draw(canvas); 230 d.setAlpha(0xFF); 231 } 232 233 if (!done) { 234 invalidateSelf(); 235 } 236 } 237 238 /** 239 * Enables or disables the cross fade of the drawables. When cross fade 240 * is disabled, the first drawable is always drawn opaque. With cross 241 * fade enabled, the first drawable is drawn with the opposite alpha of 242 * the second drawable. Cross fade is disabled by default. 243 * 244 * @param enabled True to enable cross fading, false otherwise. 245 */ setCrossFadeEnabled(boolean enabled)246 public void setCrossFadeEnabled(boolean enabled) { 247 mCrossFade = enabled; 248 } 249 250 /** 251 * Indicates whether the cross fade is enabled for this transition. 252 * 253 * @return True if cross fading is enabled, false otherwise. 254 */ isCrossFadeEnabled()255 public boolean isCrossFadeEnabled() { 256 return mCrossFade; 257 } 258 259 static class TransitionState extends LayerState { TransitionState(TransitionState orig, TransitionDrawable owner, Resources res)260 TransitionState(TransitionState orig, TransitionDrawable owner, Resources res) { 261 super(orig, owner, res); 262 } 263 264 @Override newDrawable()265 public Drawable newDrawable() { 266 return new TransitionDrawable(this, (Resources) null); 267 } 268 269 @Override newDrawable(Resources res)270 public Drawable newDrawable(Resources res) { 271 return new TransitionDrawable(this, res); 272 } 273 274 @Override getChangingConfigurations()275 public @Config int getChangingConfigurations() { 276 return mChangingConfigurations; 277 } 278 } 279 } 280