blob: 2cf8ea88cc954b8d0d1093614b1f3ad85128f14f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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.view.animation;
18
19import android.content.Context;
20import android.content.res.TypedArray;
Chet Haasec861bd72011-09-06 07:12:44 -070021import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.util.AttributeSet;
23import android.graphics.RectF;
24
25import java.util.ArrayList;
26import java.util.List;
27
28/**
29 * Represents a group of Animations that should be played together.
30 * The transformation of each individual animation are composed
31 * together into a single transform.
32 * If AnimationSet sets any properties that its children also set
33 * (for example, duration or fillBefore), the values of AnimationSet
34 * override the child values.
Chet Haasec861bd72011-09-06 07:12:44 -070035 *
36 * <p>The way that AnimationSet inherits behavior from Animation is important to
37 * understand. Some of the Animation attributes applied to AnimationSet affect the
38 * AnimationSet itself, some are pushed down to the children, and some are ignored,
39 * as follows:
40 * <ul>
41 * <li>duration, repeatMode, fillBefore, fillAfter: These properties, when set
42 * on an AnimationSet object, will be pushed down to all child animations.</li>
43 * <li>repeatCount, fillEnabled: These properties are ignored for AnimationSet.</li>
44 * <li>startOffset, shareInterpolator: These properties apply to the AnimationSet itself.</li>
45 * </ul>
46 * Starting with {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH},
47 * the behavior of these properties is the same in XML resources and at runtime (prior to that
48 * release, the values set in XML were ignored for AnimationSet). That is, calling
49 * <code>setDuration(500)</code> on an AnimationSet has the same effect as declaring
50 * <code>android:duration="500"</code> in an XML resource for an AnimationSet object.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051 */
52public class AnimationSet extends Animation {
53 private static final int PROPERTY_FILL_AFTER_MASK = 0x1;
54 private static final int PROPERTY_FILL_BEFORE_MASK = 0x2;
55 private static final int PROPERTY_REPEAT_MODE_MASK = 0x4;
56 private static final int PROPERTY_START_OFFSET_MASK = 0x8;
57 private static final int PROPERTY_SHARE_INTERPOLATOR_MASK = 0x10;
58 private static final int PROPERTY_DURATION_MASK = 0x20;
59 private static final int PROPERTY_MORPH_MATRIX_MASK = 0x40;
60 private static final int PROPERTY_CHANGE_BOUNDS_MASK = 0x80;
61
62 private int mFlags = 0;
Romain Guyd4745a62011-07-22 16:03:07 -070063 private boolean mDirty;
64 private boolean mHasAlpha;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065
66 private ArrayList<Animation> mAnimations = new ArrayList<Animation>();
67
68 private Transformation mTempTransformation = new Transformation();
69
70 private long mLastEnd;
71
72 private long[] mStoredOffsets;
73
74 /**
The Android Open Source Projectbdbdc4f2009-03-03 21:00:54 -080075 * Constructor used when an AnimationSet is loaded from a resource.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 *
77 * @param context Application context to use
78 * @param attrs Attribute set from which to read values
79 */
80 public AnimationSet(Context context, AttributeSet attrs) {
81 super(context, attrs);
82
83 TypedArray a =
84 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimationSet);
85
86 setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK,
87 a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true));
88 init();
Chet Haasec861bd72011-09-06 07:12:44 -070089
90 if (context.getApplicationInfo().targetSdkVersion >=
91 Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Dianne Hackborn89620282011-09-11 12:47:45 -070092 if (a.hasValue(com.android.internal.R.styleable.AnimationSet_duration)) {
Chet Haasec861bd72011-09-06 07:12:44 -070093 mFlags |= PROPERTY_DURATION_MASK;
94 }
Dianne Hackborn89620282011-09-11 12:47:45 -070095 if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillBefore)) {
Chet Haasec861bd72011-09-06 07:12:44 -070096 mFlags |= PROPERTY_FILL_BEFORE_MASK;
97 }
Dianne Hackborn89620282011-09-11 12:47:45 -070098 if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillAfter)) {
Chet Haasec861bd72011-09-06 07:12:44 -070099 mFlags |= PROPERTY_FILL_AFTER_MASK;
100 }
Dianne Hackborn89620282011-09-11 12:47:45 -0700101 if (a.hasValue(com.android.internal.R.styleable.AnimationSet_repeatMode)) {
Chet Haasec861bd72011-09-06 07:12:44 -0700102 mFlags |= PROPERTY_REPEAT_MODE_MASK;
103 }
Dianne Hackborn89620282011-09-11 12:47:45 -0700104 if (a.hasValue(com.android.internal.R.styleable.AnimationSet_startOffset)) {
Chet Haasec861bd72011-09-06 07:12:44 -0700105 mFlags |= PROPERTY_START_OFFSET_MASK;
106 }
107 }
108
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 a.recycle();
110 }
111
112
113 /**
114 * Constructor to use when building an AnimationSet from code
115 *
116 * @param shareInterpolator Pass true if all of the animations in this set
117 * should use the interpolator assocciated with this AnimationSet.
118 * Pass false if each animation should use its own interpolator.
119 */
120 public AnimationSet(boolean shareInterpolator) {
121 setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator);
122 init();
123 }
124
125 @Override
126 protected AnimationSet clone() throws CloneNotSupportedException {
127 final AnimationSet animation = (AnimationSet) super.clone();
128 animation.mTempTransformation = new Transformation();
129 animation.mAnimations = new ArrayList<Animation>();
130
131 final int count = mAnimations.size();
132 final ArrayList<Animation> animations = mAnimations;
133
134 for (int i = 0; i < count; i++) {
135 animation.mAnimations.add(animations.get(i).clone());
136 }
137
138 return animation;
139 }
140
141 private void setFlag(int mask, boolean value) {
142 if (value) {
143 mFlags |= mask;
144 } else {
145 mFlags &= ~mask;
146 }
147 }
148
149 private void init() {
150 mStartTime = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151 }
152
153 @Override
154 public void setFillAfter(boolean fillAfter) {
155 mFlags |= PROPERTY_FILL_AFTER_MASK;
156 super.setFillAfter(fillAfter);
157 }
158
159 @Override
160 public void setFillBefore(boolean fillBefore) {
161 mFlags |= PROPERTY_FILL_BEFORE_MASK;
162 super.setFillBefore(fillBefore);
163 }
164
165 @Override
166 public void setRepeatMode(int repeatMode) {
167 mFlags |= PROPERTY_REPEAT_MODE_MASK;
168 super.setRepeatMode(repeatMode);
169 }
170
171 @Override
172 public void setStartOffset(long startOffset) {
173 mFlags |= PROPERTY_START_OFFSET_MASK;
174 super.setStartOffset(startOffset);
175 }
176
Romain Guyc9bcebe2011-07-22 16:46:34 -0700177 /**
178 * @hide
179 */
Romain Guyd4745a62011-07-22 16:03:07 -0700180 @Override
181 public boolean hasAlpha() {
182 if (mDirty) {
183 mDirty = mHasAlpha = false;
184
185 final int count = mAnimations.size();
186 final ArrayList<Animation> animations = mAnimations;
187
188 for (int i = 0; i < count; i++) {
189 if (animations.get(i).hasAlpha()) {
190 mHasAlpha = true;
191 break;
192 }
193 }
194 }
195
196 return mHasAlpha;
197 }
198
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 /**
200 * <p>Sets the duration of every child animation.</p>
201 *
202 * @param durationMillis the duration of the animation, in milliseconds, for
203 * every child in this set
204 */
205 @Override
206 public void setDuration(long durationMillis) {
207 mFlags |= PROPERTY_DURATION_MASK;
208 super.setDuration(durationMillis);
Chet Haasec861bd72011-09-06 07:12:44 -0700209 mLastEnd = mStartOffset + mDuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 }
211
212 /**
213 * Add a child animation to this animation set.
214 * The transforms of the child animations are applied in the order
215 * that they were added
216 * @param a Animation to add.
217 */
218 public void addAnimation(Animation a) {
219 mAnimations.add(a);
220
221 boolean noMatrix = (mFlags & PROPERTY_MORPH_MATRIX_MASK) == 0;
222 if (noMatrix && a.willChangeTransformationMatrix()) {
223 mFlags |= PROPERTY_MORPH_MATRIX_MASK;
224 }
225
226 boolean changeBounds = (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == 0;
227 if (changeBounds && a.willChangeTransformationMatrix()) {
228 mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
229 }
230
Chet Haasec861bd72011-09-06 07:12:44 -0700231 if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 mLastEnd = mStartOffset + mDuration;
233 } else {
Chet Haasec861bd72011-09-06 07:12:44 -0700234 if (mAnimations.size() == 1) {
235 mDuration = a.getStartOffset() + a.getDuration();
236 mLastEnd = mStartOffset + mDuration;
237 } else {
238 mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration());
239 mDuration = mLastEnd - mStartOffset;
240 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 }
Romain Guyd4745a62011-07-22 16:03:07 -0700242
243 mDirty = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 }
245
246 /**
247 * Sets the start time of this animation and all child animations
248 *
249 * @see android.view.animation.Animation#setStartTime(long)
250 */
251 @Override
252 public void setStartTime(long startTimeMillis) {
253 super.setStartTime(startTimeMillis);
254
255 final int count = mAnimations.size();
256 final ArrayList<Animation> animations = mAnimations;
257
258 for (int i = 0; i < count; i++) {
259 Animation a = animations.get(i);
260 a.setStartTime(startTimeMillis);
261 }
262 }
263
264 @Override
265 public long getStartTime() {
266 long startTime = Long.MAX_VALUE;
267
268 final int count = mAnimations.size();
269 final ArrayList<Animation> animations = mAnimations;
270
271 for (int i = 0; i < count; i++) {
272 Animation a = animations.get(i);
273 startTime = Math.min(startTime, a.getStartTime());
274 }
275
276 return startTime;
277 }
278
279 @Override
280 public void restrictDuration(long durationMillis) {
281 super.restrictDuration(durationMillis);
282
283 final ArrayList<Animation> animations = mAnimations;
284 int count = animations.size();
285
286 for (int i = 0; i < count; i++) {
287 animations.get(i).restrictDuration(durationMillis);
288 }
289 }
290
291 /**
292 * The duration of an AnimationSet is defined to be the
293 * duration of the longest child animation.
294 *
295 * @see android.view.animation.Animation#getDuration()
296 */
297 @Override
298 public long getDuration() {
299 final ArrayList<Animation> animations = mAnimations;
300 final int count = animations.size();
301 long duration = 0;
302
303 boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
304 if (durationSet) {
305 duration = mDuration;
306 } else {
307 for (int i = 0; i < count; i++) {
308 duration = Math.max(duration, animations.get(i).getDuration());
309 }
310 }
311
312 return duration;
313 }
314
315 /**
316 * The duration hint of an animation set is the maximum of the duration
317 * hints of all of its component animations.
318 *
319 * @see android.view.animation.Animation#computeDurationHint
320 */
321 public long computeDurationHint() {
322 long duration = 0;
323 final int count = mAnimations.size();
324 final ArrayList<Animation> animations = mAnimations;
325 for (int i = count - 1; i >= 0; --i) {
326 final long d = animations.get(i).computeDurationHint();
327 if (d > duration) duration = d;
328 }
329 return duration;
330 }
331
332 /**
333 * @hide
334 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
336 final RectF region = mPreviousRegion;
337 region.set(left, top, right, bottom);
The Android Open Source Project4df24232009-03-05 14:34:35 -0800338 region.inset(-1.0f, -1.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800339
340 if (mFillBefore) {
341 final int count = mAnimations.size();
342 final ArrayList<Animation> animations = mAnimations;
343 final Transformation temp = mTempTransformation;
344
345 final Transformation previousTransformation = mPreviousTransformation;
346
347 for (int i = count - 1; i >= 0; --i) {
348 final Animation a = animations.get(i);
349
350 temp.clear();
Andreas Agvard4766def2010-02-05 08:16:01 +0100351 final Interpolator interpolator = a.mInterpolator;
352 a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0.0f)
353 : 0.0f, temp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 previousTransformation.compose(temp);
355 }
356 }
357 }
358
359 /**
360 * The transformation of an animation set is the concatenation of all of its
361 * component animations.
362 *
363 * @see android.view.animation.Animation#getTransformation
364 */
365 @Override
366 public boolean getTransformation(long currentTime, Transformation t) {
367 final int count = mAnimations.size();
368 final ArrayList<Animation> animations = mAnimations;
369 final Transformation temp = mTempTransformation;
370
371 boolean more = false;
372 boolean started = false;
373 boolean ended = true;
374
375 t.clear();
376
377 for (int i = count - 1; i >= 0; --i) {
378 final Animation a = animations.get(i);
379
380 temp.clear();
Chet Haase48460322010-06-11 14:22:25 -0700381 more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 t.compose(temp);
383
384 started = started || a.hasStarted();
385 ended = a.hasEnded() && ended;
386 }
387
388 if (started && !mStarted) {
389 if (mListener != null) {
390 mListener.onAnimationStart(this);
391 }
392 mStarted = true;
393 }
394
395 if (ended != mEnded) {
396 if (mListener != null) {
397 mListener.onAnimationEnd(this);
398 }
399 mEnded = ended;
400 }
401
402 return more;
403 }
404
405 /**
406 * @see android.view.animation.Animation#scaleCurrentDuration(float)
407 */
408 @Override
409 public void scaleCurrentDuration(float scale) {
410 final ArrayList<Animation> animations = mAnimations;
411 int count = animations.size();
412 for (int i = 0; i < count; i++) {
413 animations.get(i).scaleCurrentDuration(scale);
414 }
415 }
416
417 /**
418 * @see android.view.animation.Animation#initialize(int, int, int, int)
419 */
420 @Override
421 public void initialize(int width, int height, int parentWidth, int parentHeight) {
422 super.initialize(width, height, parentWidth, parentHeight);
423
424 boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
425 boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK;
426 boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK;
427 boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK;
428 boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK)
429 == PROPERTY_SHARE_INTERPOLATOR_MASK;
430 boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
431 == PROPERTY_START_OFFSET_MASK;
432
433 if (shareInterpolator) {
434 ensureInterpolator();
435 }
436
437 final ArrayList<Animation> children = mAnimations;
438 final int count = children.size();
439
440 final long duration = mDuration;
441 final boolean fillAfter = mFillAfter;
442 final boolean fillBefore = mFillBefore;
443 final int repeatMode = mRepeatMode;
444 final Interpolator interpolator = mInterpolator;
445 final long startOffset = mStartOffset;
446
447
448 long[] storedOffsets = mStoredOffsets;
The Android Open Source Project4df24232009-03-05 14:34:35 -0800449 if (startOffsetSet) {
450 if (storedOffsets == null || storedOffsets.length != count) {
451 storedOffsets = mStoredOffsets = new long[count];
452 }
453 } else if (storedOffsets != null) {
454 storedOffsets = mStoredOffsets = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 }
456
457 for (int i = 0; i < count; i++) {
458 Animation a = children.get(i);
459 if (durationSet) {
460 a.setDuration(duration);
461 }
462 if (fillAfterSet) {
463 a.setFillAfter(fillAfter);
464 }
465 if (fillBeforeSet) {
466 a.setFillBefore(fillBefore);
467 }
468 if (repeatModeSet) {
469 a.setRepeatMode(repeatMode);
470 }
471 if (shareInterpolator) {
472 a.setInterpolator(interpolator);
473 }
474 if (startOffsetSet) {
475 long offset = a.getStartOffset();
476 a.setStartOffset(offset + startOffset);
477 storedOffsets[i] = offset;
478 }
479 a.initialize(width, height, parentWidth, parentHeight);
480 }
481 }
482
483 @Override
484 public void reset() {
485 super.reset();
486 restoreChildrenStartOffset();
487 }
488
489 /**
490 * @hide
491 */
492 void restoreChildrenStartOffset() {
493 final long[] offsets = mStoredOffsets;
494 if (offsets == null) return;
495
496 final ArrayList<Animation> children = mAnimations;
497 final int count = children.size();
498
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499 for (int i = 0; i < count; i++) {
500 children.get(i).setStartOffset(offsets[i]);
501 }
502 }
503
504 /**
505 * @return All the child animations in this AnimationSet. Note that
506 * this may include other AnimationSets, which are not expanded.
507 */
508 public List<Animation> getAnimations() {
509 return mAnimations;
510 }
511
512 @Override
513 public boolean willChangeTransformationMatrix() {
514 return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
515 }
516
517 @Override
518 public boolean willChangeBounds() {
519 return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
520 }
521}