Home | History | Annotate | Download | only in animation
      1 /*
      2  * Copyright (C) 2014 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.animation;
     18 
     19 import android.content.res.ConstantState;
     20 import android.util.StateSet;
     21 import android.view.View;
     22 
     23 import java.lang.ref.WeakReference;
     24 import java.util.ArrayList;
     25 
     26 /**
     27  * Lets you define a number of Animators that will run on the attached View depending on the View's
     28  * drawable state.
     29  * <p>
     30  * It can be defined in an XML file with the <code>&lt;selector></code> element.
     31  * Each State Animator is defined in a nested <code>&lt;item></code> element.
     32  *
     33  * @attr ref android.R.styleable#DrawableStates_state_focused
     34  * @attr ref android.R.styleable#DrawableStates_state_window_focused
     35  * @attr ref android.R.styleable#DrawableStates_state_enabled
     36  * @attr ref android.R.styleable#DrawableStates_state_checkable
     37  * @attr ref android.R.styleable#DrawableStates_state_checked
     38  * @attr ref android.R.styleable#DrawableStates_state_selected
     39  * @attr ref android.R.styleable#DrawableStates_state_activated
     40  * @attr ref android.R.styleable#DrawableStates_state_active
     41  * @attr ref android.R.styleable#DrawableStates_state_single
     42  * @attr ref android.R.styleable#DrawableStates_state_first
     43  * @attr ref android.R.styleable#DrawableStates_state_middle
     44  * @attr ref android.R.styleable#DrawableStates_state_last
     45  * @attr ref android.R.styleable#DrawableStates_state_pressed
     46  * @attr ref android.R.styleable#StateListAnimatorItem_animation
     47  */
     48 public class StateListAnimator implements Cloneable {
     49 
     50     private ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
     51     private Tuple mLastMatch = null;
     52     private Animator mRunningAnimator = null;
     53     private WeakReference<View> mViewRef;
     54     private StateListAnimatorConstantState mConstantState;
     55     private AnimatorListenerAdapter mAnimatorListener;
     56     private int mChangingConfigurations;
     57 
     58     public StateListAnimator() {
     59         initAnimatorListener();
     60     }
     61 
     62     private void initAnimatorListener() {
     63         mAnimatorListener = new AnimatorListenerAdapter() {
     64             @Override
     65             public void onAnimationEnd(Animator animation) {
     66                 animation.setTarget(null);
     67                 if (mRunningAnimator == animation) {
     68                     mRunningAnimator = null;
     69                 }
     70             }
     71         };
     72     }
     73 
     74     /**
     75      * Associates the given animator with the provided drawable state specs so that it will be run
     76      * when the View's drawable state matches the specs.
     77      *
     78      * @param specs The drawable state specs to match against
     79      * @param animator The animator to run when the specs match
     80      */
     81     public void addState(int[] specs, Animator animator) {
     82         Tuple tuple = new Tuple(specs, animator);
     83         tuple.mAnimator.addListener(mAnimatorListener);
     84         mTuples.add(tuple);
     85         mChangingConfigurations |= animator.getChangingConfigurations();
     86     }
     87 
     88     /**
     89      * Returns the current {@link android.animation.Animator} which is started because of a state
     90      * change.
     91      *
     92      * @return The currently running Animator or null if no Animator is running
     93      * @hide
     94      */
     95     public Animator getRunningAnimator() {
     96         return mRunningAnimator;
     97     }
     98 
     99     /**
    100      * @hide
    101      */
    102     public View getTarget() {
    103         return mViewRef == null ? null : mViewRef.get();
    104     }
    105 
    106     /**
    107      * Called by View
    108      * @hide
    109      */
    110     public void setTarget(View view) {
    111         final View current = getTarget();
    112         if (current == view) {
    113             return;
    114         }
    115         if (current != null) {
    116             clearTarget();
    117         }
    118         if (view != null) {
    119             mViewRef = new WeakReference<View>(view);
    120         }
    121 
    122     }
    123 
    124     private void clearTarget() {
    125         final int size = mTuples.size();
    126         for (int i = 0; i < size; i++) {
    127             mTuples.get(i).mAnimator.setTarget(null);
    128         }
    129         mViewRef = null;
    130         mLastMatch = null;
    131         mRunningAnimator = null;
    132     }
    133 
    134     @Override
    135     public StateListAnimator clone() {
    136         try {
    137             StateListAnimator clone = (StateListAnimator) super.clone();
    138             clone.mTuples = new ArrayList<Tuple>(mTuples.size());
    139             clone.mLastMatch = null;
    140             clone.mRunningAnimator = null;
    141             clone.mViewRef = null;
    142             clone.mAnimatorListener = null;
    143             clone.initAnimatorListener();
    144             final int tupleSize = mTuples.size();
    145             for (int i = 0; i < tupleSize; i++) {
    146                 final Tuple tuple = mTuples.get(i);
    147                 final Animator animatorClone = tuple.mAnimator.clone();
    148                 animatorClone.removeListener(mAnimatorListener);
    149                 clone.addState(tuple.mSpecs, animatorClone);
    150             }
    151             clone.setChangingConfigurations(getChangingConfigurations());
    152             return clone;
    153         } catch (CloneNotSupportedException e) {
    154             throw new AssertionError("cannot clone state list animator", e);
    155         }
    156     }
    157 
    158     /**
    159      * Called by View
    160      * @hide
    161      */
    162     public void setState(int[] state) {
    163         Tuple match = null;
    164         final int count = mTuples.size();
    165         for (int i = 0; i < count; i++) {
    166             final Tuple tuple = mTuples.get(i);
    167             if (StateSet.stateSetMatches(tuple.mSpecs, state)) {
    168                 match = tuple;
    169                 break;
    170             }
    171         }
    172         if (match == mLastMatch) {
    173             return;
    174         }
    175         if (mLastMatch != null) {
    176             cancel();
    177         }
    178         mLastMatch = match;
    179         if (match != null) {
    180             start(match);
    181         }
    182     }
    183 
    184     private void start(Tuple match) {
    185         match.mAnimator.setTarget(getTarget());
    186         mRunningAnimator = match.mAnimator;
    187         mRunningAnimator.start();
    188     }
    189 
    190     private void cancel() {
    191         if (mRunningAnimator != null) {
    192             mRunningAnimator.cancel();
    193             mRunningAnimator = null;
    194         }
    195     }
    196 
    197     /**
    198      * @hide
    199      */
    200     public ArrayList<Tuple> getTuples() {
    201         return mTuples;
    202     }
    203 
    204     /**
    205      * If there is an animation running for a recent state change, ends it.
    206      * <p>
    207      * This causes the animation to assign the end value(s) to the View.
    208      */
    209     public void jumpToCurrentState() {
    210         if (mRunningAnimator != null) {
    211             mRunningAnimator.end();
    212         }
    213     }
    214 
    215     /**
    216      * Return a mask of the configuration parameters for which this animator may change, requiring
    217      * that it be re-created.  The default implementation returns whatever was provided through
    218      * {@link #setChangingConfigurations(int)} or 0 by default.
    219      *
    220      * @return Returns a mask of the changing configuration parameters, as defined by
    221      * {@link android.content.pm.ActivityInfo}.
    222      *
    223      * @see android.content.pm.ActivityInfo
    224      * @hide
    225      */
    226     public int getChangingConfigurations() {
    227         return mChangingConfigurations;
    228     }
    229 
    230     /**
    231      * Set a mask of the configuration parameters for which this animator may change, requiring
    232      * that it should be recreated from resources instead of being cloned.
    233      *
    234      * @param configs A mask of the changing configuration parameters, as
    235      * defined by {@link android.content.pm.ActivityInfo}.
    236      *
    237      * @see android.content.pm.ActivityInfo
    238      * @hide
    239      */
    240     public void setChangingConfigurations(int configs) {
    241         mChangingConfigurations = configs;
    242     }
    243 
    244     /**
    245      * Sets the changing configurations value to the union of the current changing configurations
    246      * and the provided configs.
    247      * This method is called while loading the animator.
    248      * @hide
    249      */
    250     public void appendChangingConfigurations(int configs) {
    251         mChangingConfigurations |= configs;
    252     }
    253 
    254     /**
    255      * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
    256      * this Animator.
    257      * <p>
    258      * This constant state is used to create new instances of this animator when needed. Default
    259      * implementation creates a new {@link StateListAnimatorConstantState}. You can override this
    260      * method to provide your custom logic or return null if you don't want this animator to be
    261      * cached.
    262      *
    263      * @return The {@link android.content.res.ConstantState} associated to this Animator.
    264      * @see android.content.res.ConstantState
    265      * @see #clone()
    266      * @hide
    267      */
    268     public ConstantState<StateListAnimator> createConstantState() {
    269         return new StateListAnimatorConstantState(this);
    270     }
    271 
    272     /**
    273      * @hide
    274      */
    275     public static class Tuple {
    276 
    277         final int[] mSpecs;
    278 
    279         final Animator mAnimator;
    280 
    281         private Tuple(int[] specs, Animator animator) {
    282             mSpecs = specs;
    283             mAnimator = animator;
    284         }
    285 
    286         /**
    287          * @hide
    288          */
    289         public int[] getSpecs() {
    290             return mSpecs;
    291         }
    292 
    293         /**
    294          * @hide
    295          */
    296         public Animator getAnimator() {
    297             return mAnimator;
    298         }
    299     }
    300 
    301     /**
    302      * Creates a constant state which holds changing configurations information associated with the
    303      * given Animator.
    304      * <p>
    305      * When new instance is called, default implementation clones the Animator.
    306      */
    307     private static class StateListAnimatorConstantState
    308             extends ConstantState<StateListAnimator> {
    309 
    310         final StateListAnimator mAnimator;
    311 
    312         int mChangingConf;
    313 
    314         public StateListAnimatorConstantState(StateListAnimator animator) {
    315             mAnimator = animator;
    316             mAnimator.mConstantState = this;
    317             mChangingConf = mAnimator.getChangingConfigurations();
    318         }
    319 
    320         @Override
    321         public int getChangingConfigurations() {
    322             return mChangingConf;
    323         }
    324 
    325         @Override
    326         public StateListAnimator newInstance() {
    327             final StateListAnimator clone = mAnimator.clone();
    328             clone.mConstantState = this;
    329             return clone;
    330         }
    331     }
    332 }
    333