blob: d49e914cf5cca707cbffd43812f8afc63c91457e [file] [log] [blame]
Yigit Boyarf4c5bf32014-05-06 16:56:08 -07001/*
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
17package android.animation;
18
Yigit Boyard422dc32014-09-25 12:23:35 -070019import android.content.res.ConstantState;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070020import android.util.StateSet;
21import android.view.View;
22
23import java.lang.ref.WeakReference;
24import 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 */
Yigit Boyard422dc32014-09-25 12:23:35 -070048public class StateListAnimator implements Cloneable {
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070049
Yigit Boyard422dc32014-09-25 12:23:35 -070050 private ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070051 private Tuple mLastMatch = null;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070052 private Animator mRunningAnimator = null;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070053 private WeakReference<View> mViewRef;
Yigit Boyard422dc32014-09-25 12:23:35 -070054 private StateListAnimatorConstantState mConstantState;
55 private AnimatorListenerAdapter mAnimatorListener;
56 private int mChangingConfigurations;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070057
Yigit Boyard422dc32014-09-25 12:23:35 -070058 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 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070070 }
Yigit Boyard422dc32014-09-25 12:23:35 -070071 };
72 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070073
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);
Yigit Boyard422dc32014-09-25 12:23:35 -070085 mChangingConfigurations |= animator.getChangingConfigurations();
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070086 }
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 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700129 mViewRef = null;
130 mLastMatch = null;
131 mRunningAnimator = null;
132 }
133
Yigit Boyard422dc32014-09-25 12:23:35 -0700134 @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
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700158 /**
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) {
Yigit Boyarde5a75e2014-06-16 23:31:02 -0700176 cancel();
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700177 }
178 mLastMatch = match;
179 if (match != null) {
180 start(match);
181 }
182 }
183
184 private void start(Tuple match) {
185 match.mAnimator.setTarget(getTarget());
Yigit Boyar8619f482014-07-15 17:28:07 -0700186 mRunningAnimator = match.mAnimator;
Yigit Boyarde5a75e2014-06-16 23:31:02 -0700187 mRunningAnimator.start();
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700188 }
189
Yigit Boyarde5a75e2014-06-16 23:31:02 -0700190 private void cancel() {
191 if (mRunningAnimator != null) {
192 mRunningAnimator.cancel();
193 mRunningAnimator = null;
194 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700195 }
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 /**
Yigit Boyard422dc32014-09-25 12:23:35 -0700216 * 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 /**
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700273 * @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 }
Yigit Boyard422dc32014-09-25 12:23:35 -0700300
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 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700332}