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