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.compat.annotation.UnsupportedAppUsage; 20 import android.content.pm.ActivityInfo.Config; 21 import android.content.res.Resources; 22 import android.graphics.Canvas; 23 import android.os.SystemClock; 24 25 /** 26 * An extension of LayerDrawables that is intended to cross-fade between 27 * the first and second layer. To start the transition, call {@link #startTransition(int)}. To 28 * display just the first layer, call {@link #resetTransition()}. 29 * <p> 30 * It can be defined in an XML file with the <code><transition></code> element. 31 * Each Drawable in the transition is defined in a nested <code><item></code>. For more 32 * information, see the guide to <a 33 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 34 * 35 * @attr ref android.R.styleable#LayerDrawableItem_left 36 * @attr ref android.R.styleable#LayerDrawableItem_top 37 * @attr ref android.R.styleable#LayerDrawableItem_right 38 * @attr ref android.R.styleable#LayerDrawableItem_bottom 39 * @attr ref android.R.styleable#LayerDrawableItem_drawable 40 * @attr ref android.R.styleable#LayerDrawableItem_id 41 * 42 */ 43 public class TransitionDrawable extends LayerDrawable implements Drawable.Callback { 44 45 /** 46 * A transition is about to start. 47 */ 48 private static final int TRANSITION_STARTING = 0; 49 50 /** 51 * The transition has started and the animation is in progress 52 */ 53 private static final int TRANSITION_RUNNING = 1; 54 55 /** 56 * No transition will be applied 57 */ 58 private static final int TRANSITION_NONE = 2; 59 60 /** 61 * The current state of the transition. One of {@link #TRANSITION_STARTING}, 62 * {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE} 63 */ 64 private int mTransitionState = TRANSITION_NONE; 65 66 private boolean mReverse; 67 private long mStartTimeMillis; 68 private int mFrom; 69 @UnsupportedAppUsage 70 private int mTo; 71 private int mDuration; 72 private int mOriginalDuration; 73 @UnsupportedAppUsage 74 private int mAlpha = 0; 75 @UnsupportedAppUsage 76 private boolean mCrossFade; 77 78 /** 79 * Create a new transition drawable with the specified list of layers. At least 80 * 2 layers are required for this drawable to work properly. 81 */ TransitionDrawable(Drawable[] layers)82 public TransitionDrawable(Drawable[] layers) { 83 this(new TransitionState(null, null, null), layers); 84 } 85 86 /** 87 * Create a new transition drawable with no layer. To work correctly, at least 2 88 * layers must be added to this drawable. 89 * 90 * @see #TransitionDrawable(Drawable[]) 91 */ TransitionDrawable()92 TransitionDrawable() { 93 this(new TransitionState(null, null, null), (Resources) null); 94 } 95 TransitionDrawable(TransitionState state, Resources res)96 private TransitionDrawable(TransitionState state, Resources res) { 97 super(state, res); 98 } 99 TransitionDrawable(TransitionState state, Drawable[] layers)100 private TransitionDrawable(TransitionState state, Drawable[] layers) { 101 super(layers, state); 102 } 103 104 @Override createConstantState(LayerState state, Resources res)105 LayerState createConstantState(LayerState state, Resources res) { 106 return new TransitionState((TransitionState) state, this, res); 107 } 108 109 /** 110 * Begin the second layer on top of the first layer. 111 * 112 * @param durationMillis The length of the transition in milliseconds 113 */ startTransition(int durationMillis)114 public void startTransition(int durationMillis) { 115 mFrom = 0; 116 mTo = 255; 117 mAlpha = 0; 118 mDuration = mOriginalDuration = durationMillis; 119 mReverse = false; 120 mTransitionState = TRANSITION_STARTING; 121 invalidateSelf(); 122 } 123 124 /** 125 * Show the second layer on top of the first layer immediately 126 * 127 * @hide 128 */ showSecondLayer()129 public void showSecondLayer() { 130 mAlpha = 255; 131 mReverse = false; 132 mTransitionState = TRANSITION_NONE; 133 invalidateSelf(); 134 } 135 136 /** 137 * Show only the first layer. 138 */ resetTransition()139 public void resetTransition() { 140 mAlpha = 0; 141 mTransitionState = TRANSITION_NONE; 142 invalidateSelf(); 143 } 144 145 /** 146 * Reverses the transition, picking up where the transition currently is. 147 * If the transition is not currently running, this will start the transition 148 * with the specified duration. If the transition is already running, the last 149 * known duration will be used. 150 * 151 * @param duration The duration to use if no transition is running. 152 */ reverseTransition(int duration)153 public void reverseTransition(int duration) { 154 final long time = SystemClock.uptimeMillis(); 155 // Animation is over 156 if (time - mStartTimeMillis > mDuration) { 157 if (mTo == 0) { 158 mFrom = 0; 159 mTo = 255; 160 mAlpha = 0; 161 mReverse = false; 162 } else { 163 mFrom = 255; 164 mTo = 0; 165 mAlpha = 255; 166 mReverse = true; 167 } 168 mDuration = mOriginalDuration = duration; 169 mTransitionState = TRANSITION_STARTING; 170 invalidateSelf(); 171 return; 172 } 173 174 mReverse = !mReverse; 175 mFrom = mAlpha; 176 mTo = mReverse ? 0 : 255; 177 mDuration = (int) (mReverse ? time - mStartTimeMillis : 178 mOriginalDuration - (time - mStartTimeMillis)); 179 mTransitionState = TRANSITION_STARTING; 180 } 181 182 @Override draw(Canvas canvas)183 public void draw(Canvas canvas) { 184 boolean done = true; 185 186 switch (mTransitionState) { 187 case TRANSITION_STARTING: 188 mStartTimeMillis = SystemClock.uptimeMillis(); 189 done = false; 190 mTransitionState = TRANSITION_RUNNING; 191 break; 192 193 case TRANSITION_RUNNING: 194 if (mStartTimeMillis >= 0) { 195 float normalized = (float) 196 (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration; 197 done = normalized >= 1.0f; 198 normalized = Math.min(normalized, 1.0f); 199 mAlpha = (int) (mFrom + (mTo - mFrom) * normalized); 200 } 201 break; 202 } 203 204 final int alpha = mAlpha; 205 final boolean crossFade = mCrossFade; 206 final ChildDrawable[] array = mLayerState.mChildren; 207 208 if (done) { 209 // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw 210 // the appropriate drawable[s] and return 211 if (!crossFade || alpha == 0) { 212 array[0].mDrawable.draw(canvas); 213 } 214 if (alpha == 0xFF) { 215 array[1].mDrawable.draw(canvas); 216 } 217 return; 218 } 219 220 Drawable d; 221 d = array[0].mDrawable; 222 if (crossFade) { 223 d.setAlpha(255 - alpha); 224 } 225 d.draw(canvas); 226 if (crossFade) { 227 d.setAlpha(0xFF); 228 } 229 230 if (alpha > 0) { 231 d = array[1].mDrawable; 232 d.setAlpha(alpha); 233 d.draw(canvas); 234 d.setAlpha(0xFF); 235 } 236 237 if (!done) { 238 invalidateSelf(); 239 } 240 } 241 242 /** 243 * Enables or disables the cross fade of the drawables. When cross fade 244 * is disabled, the first drawable is always drawn opaque. With cross 245 * fade enabled, the first drawable is drawn with the opposite alpha of 246 * the second drawable. Cross fade is disabled by default. 247 * 248 * @param enabled True to enable cross fading, false otherwise. 249 */ setCrossFadeEnabled(boolean enabled)250 public void setCrossFadeEnabled(boolean enabled) { 251 mCrossFade = enabled; 252 } 253 254 /** 255 * Indicates whether the cross fade is enabled for this transition. 256 * 257 * @return True if cross fading is enabled, false otherwise. 258 */ isCrossFadeEnabled()259 public boolean isCrossFadeEnabled() { 260 return mCrossFade; 261 } 262 263 static class TransitionState extends LayerState { TransitionState(TransitionState orig, TransitionDrawable owner, Resources res)264 TransitionState(TransitionState orig, TransitionDrawable owner, Resources res) { 265 super(orig, owner, res); 266 } 267 268 @Override newDrawable()269 public Drawable newDrawable() { 270 return new TransitionDrawable(this, (Resources) null); 271 } 272 273 @Override newDrawable(Resources res)274 public Drawable newDrawable(Resources res) { 275 return new TransitionDrawable(this, res); 276 } 277 278 @Override getChangingConfigurations()279 public @Config int getChangingConfigurations() { 280 return mChangingConfigurations; 281 } 282 } 283 } 284