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.res.Resources; 20 import android.graphics.Canvas; 21 import 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><transition></code> element. 29 * Each Drawable in the transition is defined in a nested <code><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 */ 41 public 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 */ TransitionDrawable(Drawable[] layers)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 */ TransitionDrawable()87 TransitionDrawable() { 88 this(new TransitionState(null, null, null), (Resources)null); 89 } 90 TransitionDrawable(TransitionState state, Resources res)91 private TransitionDrawable(TransitionState state, Resources res) { 92 super(state, res); 93 } 94 TransitionDrawable(TransitionState state, Drawable[] layers)95 private TransitionDrawable(TransitionState state, Drawable[] layers) { 96 super(layers, state); 97 } 98 99 @Override createConstantState(LayerState state, Resources res)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 */ startTransition(int durationMillis)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 */ resetTransition()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 */ reverseTransition(int duration)136 public void reverseTransition(int duration) { 137 final long time = SystemClock.uptimeMillis(); 138 // Animation is over 139 if (time - mStartTimeMillis > mDuration) { 140 if (mTo == 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 draw(Canvas canvas)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 191 if (done) { 192 // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw 193 // the appropriate drawable[s] and return 194 if (!crossFade || alpha == 0) { 195 array[0].mDrawable.draw(canvas); 196 } 197 if (alpha == 0xFF) { 198 array[1].mDrawable.draw(canvas); 199 } 200 return; 201 } 202 203 Drawable d; 204 d = array[0].mDrawable; 205 if (crossFade) { 206 d.setAlpha(255 - alpha); 207 } 208 d.draw(canvas); 209 if (crossFade) { 210 d.setAlpha(0xFF); 211 } 212 213 if (alpha > 0) { 214 d = array[1].mDrawable; 215 d.setAlpha(alpha); 216 d.draw(canvas); 217 d.setAlpha(0xFF); 218 } 219 220 if (!done) { 221 invalidateSelf(); 222 } 223 } 224 225 /** 226 * Enables or disables the cross fade of the drawables. When cross fade 227 * is disabled, the first drawable is always drawn opaque. With cross 228 * fade enabled, the first drawable is drawn with the opposite alpha of 229 * the second drawable. Cross fade is disabled by default. 230 * 231 * @param enabled True to enable cross fading, false otherwise. 232 */ setCrossFadeEnabled(boolean enabled)233 public void setCrossFadeEnabled(boolean enabled) { 234 mCrossFade = enabled; 235 } 236 237 /** 238 * Indicates whether the cross fade is enabled for this transition. 239 * 240 * @return True if cross fading is enabled, false otherwise. 241 */ isCrossFadeEnabled()242 public boolean isCrossFadeEnabled() { 243 return mCrossFade; 244 } 245 246 static class TransitionState extends LayerState { TransitionState(TransitionState orig, TransitionDrawable owner, Resources res)247 TransitionState(TransitionState orig, TransitionDrawable owner, 248 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 int getChangingConfigurations() { 264 return mChangingConfigurations; 265 } 266 } 267 } 268