blob: b6d6910c1dc855b81e15caeec495fa1115abdae7 [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
Alan Viveretteac85f902016-03-11 15:15:51 -050019import android.content.pm.ActivityInfo.Config;
Yigit Boyard422dc32014-09-25 12:23:35 -070020import android.content.res.ConstantState;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070021import android.util.StateSet;
22import android.view.View;
23
24import java.lang.ref.WeakReference;
25import 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 */
Yigit Boyard422dc32014-09-25 12:23:35 -070049public class StateListAnimator implements Cloneable {
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070050
Yigit Boyard422dc32014-09-25 12:23:35 -070051 private ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070052 private Tuple mLastMatch = null;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070053 private Animator mRunningAnimator = null;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070054 private WeakReference<View> mViewRef;
Yigit Boyard422dc32014-09-25 12:23:35 -070055 private StateListAnimatorConstantState mConstantState;
56 private AnimatorListenerAdapter mAnimatorListener;
Alan Viveretteac85f902016-03-11 15:15:51 -050057 private @Config int mChangingConfigurations;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070058
Yigit Boyard422dc32014-09-25 12:23:35 -070059 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 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070071 }
Yigit Boyard422dc32014-09-25 12:23:35 -070072 };
73 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070074
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);
Yigit Boyard422dc32014-09-25 12:23:35 -070086 mChangingConfigurations |= animator.getChangingConfigurations();
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070087 }
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 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700130 mViewRef = null;
131 mLastMatch = null;
132 mRunningAnimator = null;
133 }
134
Yigit Boyard422dc32014-09-25 12:23:35 -0700135 @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
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700159 /**
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) {
Yigit Boyarde5a75e2014-06-16 23:31:02 -0700177 cancel();
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700178 }
179 mLastMatch = match;
180 if (match != null) {
181 start(match);
182 }
183 }
184
185 private void start(Tuple match) {
186 match.mAnimator.setTarget(getTarget());
Yigit Boyar8619f482014-07-15 17:28:07 -0700187 mRunningAnimator = match.mAnimator;
Yigit Boyarde5a75e2014-06-16 23:31:02 -0700188 mRunningAnimator.start();
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700189 }
190
Yigit Boyarde5a75e2014-06-16 23:31:02 -0700191 private void cancel() {
192 if (mRunningAnimator != null) {
193 mRunningAnimator.cancel();
194 mRunningAnimator = null;
195 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700196 }
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 /**
Yigit Boyard422dc32014-09-25 12:23:35 -0700217 * 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 */
Alan Viveretteac85f902016-03-11 15:15:51 -0500227 public @Config int getChangingConfigurations() {
Yigit Boyard422dc32014-09-25 12:23:35 -0700228 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 */
Alan Viveretteac85f902016-03-11 15:15:51 -0500241 public void setChangingConfigurations(@Config int configs) {
Yigit Boyard422dc32014-09-25 12:23:35 -0700242 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 */
Alan Viveretteac85f902016-03-11 15:15:51 -0500251 public void appendChangingConfigurations(@Config int configs) {
Yigit Boyard422dc32014-09-25 12:23:35 -0700252 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 /**
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700274 * @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 }
Yigit Boyard422dc32014-09-25 12:23:35 -0700301
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
Alan Viveretteac85f902016-03-11 15:15:51 -0500313 @Config int mChangingConf;
Yigit Boyard422dc32014-09-25 12:23:35 -0700314
315 public StateListAnimatorConstantState(StateListAnimator animator) {
316 mAnimator = animator;
317 mAnimator.mConstantState = this;
318 mChangingConf = mAnimator.getChangingConfigurations();
319 }
320
321 @Override
Alan Viveretteac85f902016-03-11 15:15:51 -0500322 public @Config int getChangingConfigurations() {
Yigit Boyard422dc32014-09-25 12:23:35 -0700323 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 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700333}