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 
StateListAnimator()59     public StateListAnimator() {
60         initAnimatorListener();
61     }
62 
initAnimatorListener()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      */
addState(int[] specs, Animator animator)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      */
getRunningAnimator()96     public Animator getRunningAnimator() {
97         return mRunningAnimator;
98     }
99 
100     /**
101      * @hide
102      */
getTarget()103     public View getTarget() {
104         return mViewRef == null ? null : mViewRef.get();
105     }
106 
107     /**
108      * Called by View
109      * @hide
110      */
setTarget(View view)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 
clearTarget()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
clone()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      */
setState(int[] state)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 
start(Tuple match)185     private void start(Tuple match) {
186         match.mAnimator.setTarget(getTarget());
187         mRunningAnimator = match.mAnimator;
188         mRunningAnimator.start();
189     }
190 
cancel()191     private void cancel() {
192         if (mRunningAnimator != null) {
193             mRunningAnimator.cancel();
194             mRunningAnimator = null;
195         }
196     }
197 
198     /**
199      * @hide
200      */
getTuples()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      */
jumpToCurrentState()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      */
getChangingConfigurations()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      */
setChangingConfigurations(@onfig int configs)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      */
appendChangingConfigurations(@onfig int configs)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      */
createConstantState()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 
Tuple(int[] specs, Animator animator)282         private Tuple(int[] specs, Animator animator) {
283             mSpecs = specs;
284             mAnimator = animator;
285         }
286 
287         /**
288          * @hide
289          */
getSpecs()290         public int[] getSpecs() {
291             return mSpecs;
292         }
293 
294         /**
295          * @hide
296          */
getAnimator()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 
StateListAnimatorConstantState(StateListAnimator animator)315         public StateListAnimatorConstantState(StateListAnimator animator) {
316             mAnimator = animator;
317             mAnimator.mConstantState = this;
318             mChangingConf = mAnimator.getChangingConfigurations();
319         }
320 
321         @Override
getChangingConfigurations()322         public @Config int getChangingConfigurations() {
323             return mChangingConf;
324         }
325 
326         @Override
newInstance()327         public StateListAnimator newInstance() {
328             final StateListAnimator clone = mAnimator.clone();
329             clone.mConstantState = this;
330             return clone;
331         }
332     }
333 }
334