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 only the first layer. 122 */ resetTransition()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 */ reverseTransition(int duration)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 draw(Canvas canvas)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 */ setCrossFadeEnabled(boolean enabled)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 */ isCrossFadeEnabled()243 public boolean isCrossFadeEnabled() { 244 return mCrossFade; 245 } 246 247 static class TransitionState extends LayerState { TransitionState(TransitionState orig, TransitionDrawable owner, Resources res)248 TransitionState(TransitionState orig, TransitionDrawable owner, Resources res) { 249 super(orig, owner, res); 250 } 251 252 @Override newDrawable()253 public Drawable newDrawable() { 254 return new TransitionDrawable(this, (Resources) null); 255 } 256 257 @Override newDrawable(Resources res)258 public Drawable newDrawable(Resources res) { 259 return new TransitionDrawable(this, res); 260 } 261 262 @Override getChangingConfigurations()263 public @Config int getChangingConfigurations() { 264 return mChangingConfigurations; 265 } 266 } 267 } 268