Home | History | Annotate | Download | only in drawable
      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>&lt;transition></code> element.
     29  * Each Drawable in the transition is defined in a nested <code>&lt;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      */
     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      */
     87     TransitionDrawable() {
     88         this(new TransitionState(null, null, null), (Resources) null);
     89     }
     90 
     91     private TransitionDrawable(TransitionState state, Resources res) {
     92         super(state, res);
     93     }
     94 
     95     private TransitionDrawable(TransitionState state, Drawable[] layers) {
     96         super(layers, state);
     97     }
     98 
     99     @Override
    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      */
    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      */
    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      */
    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
    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      */
    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      */
    242     public boolean isCrossFadeEnabled() {
    243         return mCrossFade;
    244     }
    245 
    246     static class TransitionState extends LayerState {
    247         TransitionState(TransitionState orig, TransitionDrawable owner, Resources res) {
    248             super(orig, owner, res);
    249         }
    250 
    251         @Override
    252         public Drawable newDrawable() {
    253             return new TransitionDrawable(this, (Resources) null);
    254         }
    255 
    256         @Override
    257         public Drawable newDrawable(Resources res) {
    258             return new TransitionDrawable(this, res);
    259         }
    260 
    261         @Override
    262         public int getChangingConfigurations() {
    263             return mChangingConfigurations;
    264         }
    265     }
    266 }
    267