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