1 /*
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 
17 package android.view.animation;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.RectF;
22 import android.os.Build;
23 import android.util.AttributeSet;
24 
25 import java.util.ArrayList;
26 import 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.
35  *
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>
51  */
52 public 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;
63     private boolean mDirty;
64     private boolean mHasAlpha;
65 
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     /**
75      * Constructor used when an AnimationSet is loaded from a resource.
76      *
77      * @param context Application context to use
78      * @param attrs Attribute set from which to read values
79      */
AnimationSet(Context context, AttributeSet attrs)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();
89 
90         if (context.getApplicationInfo().targetSdkVersion >=
91                 Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
92             if (a.hasValue(com.android.internal.R.styleable.AnimationSet_duration)) {
93                 mFlags |= PROPERTY_DURATION_MASK;
94             }
95             if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillBefore)) {
96                 mFlags |= PROPERTY_FILL_BEFORE_MASK;
97             }
98             if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillAfter)) {
99                 mFlags |= PROPERTY_FILL_AFTER_MASK;
100             }
101             if (a.hasValue(com.android.internal.R.styleable.AnimationSet_repeatMode)) {
102                 mFlags |= PROPERTY_REPEAT_MODE_MASK;
103             }
104             if (a.hasValue(com.android.internal.R.styleable.AnimationSet_startOffset)) {
105                 mFlags |= PROPERTY_START_OFFSET_MASK;
106             }
107         }
108 
109         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 associated with this AnimationSet.
118      *        Pass false if each animation should use its own interpolator.
119      */
AnimationSet(boolean shareInterpolator)120     public AnimationSet(boolean shareInterpolator) {
121         setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator);
122         init();
123     }
124 
125     @Override
clone()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 
setFlag(int mask, boolean value)141     private void setFlag(int mask, boolean value) {
142         if (value) {
143             mFlags |= mask;
144         } else {
145             mFlags &= ~mask;
146         }
147     }
148 
init()149     private void init() {
150         mStartTime = 0;
151     }
152 
153     @Override
setFillAfter(boolean fillAfter)154     public void setFillAfter(boolean fillAfter) {
155         mFlags |= PROPERTY_FILL_AFTER_MASK;
156         super.setFillAfter(fillAfter);
157     }
158 
159     @Override
setFillBefore(boolean fillBefore)160     public void setFillBefore(boolean fillBefore) {
161         mFlags |= PROPERTY_FILL_BEFORE_MASK;
162         super.setFillBefore(fillBefore);
163     }
164 
165     @Override
setRepeatMode(int repeatMode)166     public void setRepeatMode(int repeatMode) {
167         mFlags |= PROPERTY_REPEAT_MODE_MASK;
168         super.setRepeatMode(repeatMode);
169     }
170 
171     @Override
setStartOffset(long startOffset)172     public void setStartOffset(long startOffset) {
173         mFlags |= PROPERTY_START_OFFSET_MASK;
174         super.setStartOffset(startOffset);
175     }
176 
177     /**
178      * @hide
179      */
180     @Override
hasAlpha()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 
199     /**
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
setDuration(long durationMillis)206     public void setDuration(long durationMillis) {
207         mFlags |= PROPERTY_DURATION_MASK;
208         super.setDuration(durationMillis);
209         mLastEnd = mStartOffset + mDuration;
210     }
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      */
addAnimation(Animation a)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 
228 
229         if (changeBounds && a.willChangeBounds()) {
230             mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
231         }
232 
233         if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) {
234             mLastEnd = mStartOffset + mDuration;
235         } else {
236             if (mAnimations.size() == 1) {
237                 mDuration = a.getStartOffset() + a.getDuration();
238                 mLastEnd = mStartOffset + mDuration;
239             } else {
240                 mLastEnd = Math.max(mLastEnd, mStartOffset + a.getStartOffset() + a.getDuration());
241                 mDuration = mLastEnd - mStartOffset;
242             }
243         }
244 
245         mDirty = true;
246     }
247 
248     /**
249      * Sets the start time of this animation and all child animations
250      *
251      * @see android.view.animation.Animation#setStartTime(long)
252      */
253     @Override
setStartTime(long startTimeMillis)254     public void setStartTime(long startTimeMillis) {
255         super.setStartTime(startTimeMillis);
256 
257         final int count = mAnimations.size();
258         final ArrayList<Animation> animations = mAnimations;
259 
260         for (int i = 0; i < count; i++) {
261             Animation a = animations.get(i);
262             a.setStartTime(startTimeMillis);
263         }
264     }
265 
266     @Override
getStartTime()267     public long getStartTime() {
268         long startTime = Long.MAX_VALUE;
269 
270         final int count = mAnimations.size();
271         final ArrayList<Animation> animations = mAnimations;
272 
273         for (int i = 0; i < count; i++) {
274             Animation a = animations.get(i);
275             startTime = Math.min(startTime, a.getStartTime());
276         }
277 
278         return startTime;
279     }
280 
281     @Override
restrictDuration(long durationMillis)282     public void restrictDuration(long durationMillis) {
283         super.restrictDuration(durationMillis);
284 
285         final ArrayList<Animation> animations = mAnimations;
286         int count = animations.size();
287 
288         for (int i = 0; i < count; i++) {
289             animations.get(i).restrictDuration(durationMillis);
290         }
291     }
292 
293     /**
294      * The duration of an AnimationSet is defined to be the
295      * duration of the longest child animation.
296      *
297      * @see android.view.animation.Animation#getDuration()
298      */
299     @Override
getDuration()300     public long getDuration() {
301         final ArrayList<Animation> animations = mAnimations;
302         final int count = animations.size();
303         long duration = 0;
304 
305         boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
306         if (durationSet) {
307             duration = mDuration;
308         } else {
309             for (int i = 0; i < count; i++) {
310                 duration = Math.max(duration, animations.get(i).getDuration());
311             }
312         }
313 
314         return duration;
315     }
316 
317     /**
318      * The duration hint of an animation set is the maximum of the duration
319      * hints of all of its component animations.
320      *
321      * @see android.view.animation.Animation#computeDurationHint
322      */
computeDurationHint()323     public long computeDurationHint() {
324         long duration = 0;
325         final int count = mAnimations.size();
326         final ArrayList<Animation> animations = mAnimations;
327         for (int i = count - 1; i >= 0; --i) {
328             final long d = animations.get(i).computeDurationHint();
329             if (d > duration) duration = d;
330         }
331         return duration;
332     }
333 
334     /**
335      * @hide
336      */
initializeInvalidateRegion(int left, int top, int right, int bottom)337     public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
338         final RectF region = mPreviousRegion;
339         region.set(left, top, right, bottom);
340         region.inset(-1.0f, -1.0f);
341 
342         if (mFillBefore) {
343             final int count = mAnimations.size();
344             final ArrayList<Animation> animations = mAnimations;
345             final Transformation temp = mTempTransformation;
346 
347             final Transformation previousTransformation = mPreviousTransformation;
348 
349             for (int i = count - 1; i >= 0; --i) {
350                 final Animation a = animations.get(i);
351                 if (!a.isFillEnabled() || a.getFillBefore() || a.getStartOffset() == 0) {
352                     temp.clear();
353                     final Interpolator interpolator = a.mInterpolator;
354                     a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0.0f)
355                             : 0.0f, temp);
356                     previousTransformation.compose(temp);
357                 }
358             }
359         }
360     }
361 
362     /**
363      * The transformation of an animation set is the concatenation of all of its
364      * component animations.
365      *
366      * @see android.view.animation.Animation#getTransformationAt
367      * @hide
368      */
369     @Override
getTransformationAt(float interpolatedTime, Transformation t)370     public void getTransformationAt(float interpolatedTime, Transformation t) {
371         final Transformation temp = mTempTransformation;
372 
373         for (int i = mAnimations.size() - 1; i >= 0; --i) {
374             final Animation a = mAnimations.get(i);
375 
376             temp.clear();
377             a.getTransformationAt(interpolatedTime, temp);
378             t.compose(temp);
379         }
380     }
381 
382     /**
383      * The transformation of an animation set is the concatenation of all of its
384      * component animations.
385      *
386      * @see android.view.animation.Animation#getTransformation
387      */
388     @Override
getTransformation(long currentTime, Transformation t)389     public boolean getTransformation(long currentTime, Transformation t) {
390         final int count = mAnimations.size();
391         final ArrayList<Animation> animations = mAnimations;
392         final Transformation temp = mTempTransformation;
393 
394         boolean more = false;
395         boolean started = false;
396         boolean ended = true;
397 
398         t.clear();
399 
400         for (int i = count - 1; i >= 0; --i) {
401             final Animation a = animations.get(i);
402 
403             temp.clear();
404             more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
405             t.compose(temp);
406 
407             started = started || a.hasStarted();
408             ended = a.hasEnded() && ended;
409         }
410 
411         if (started && !mStarted) {
412             dispatchAnimationStart();
413             mStarted = true;
414         }
415 
416         if (ended != mEnded) {
417             dispatchAnimationEnd();
418             mEnded = ended;
419         }
420 
421         return more;
422     }
423 
424     /**
425      * @see android.view.animation.Animation#scaleCurrentDuration(float)
426      */
427     @Override
scaleCurrentDuration(float scale)428     public void scaleCurrentDuration(float scale) {
429         final ArrayList<Animation> animations = mAnimations;
430         int count = animations.size();
431         for (int i = 0; i < count; i++) {
432             animations.get(i).scaleCurrentDuration(scale);
433         }
434     }
435 
436     /**
437      * @see android.view.animation.Animation#initialize(int, int, int, int)
438      */
439     @Override
initialize(int width, int height, int parentWidth, int parentHeight)440     public void initialize(int width, int height, int parentWidth, int parentHeight) {
441         super.initialize(width, height, parentWidth, parentHeight);
442 
443         boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
444         boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK;
445         boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK;
446         boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK;
447         boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK)
448                 == PROPERTY_SHARE_INTERPOLATOR_MASK;
449         boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
450                 == PROPERTY_START_OFFSET_MASK;
451 
452         if (shareInterpolator) {
453             ensureInterpolator();
454         }
455 
456         final ArrayList<Animation> children = mAnimations;
457         final int count = children.size();
458 
459         final long duration = mDuration;
460         final boolean fillAfter = mFillAfter;
461         final boolean fillBefore = mFillBefore;
462         final int repeatMode = mRepeatMode;
463         final Interpolator interpolator = mInterpolator;
464         final long startOffset = mStartOffset;
465 
466 
467         long[] storedOffsets = mStoredOffsets;
468         if (startOffsetSet) {
469             if (storedOffsets == null || storedOffsets.length != count) {
470                 storedOffsets = mStoredOffsets = new long[count];
471             }
472         } else if (storedOffsets != null) {
473             storedOffsets = mStoredOffsets = null;
474         }
475 
476         for (int i = 0; i < count; i++) {
477             Animation a = children.get(i);
478             if (durationSet) {
479                 a.setDuration(duration);
480             }
481             if (fillAfterSet) {
482                 a.setFillAfter(fillAfter);
483             }
484             if (fillBeforeSet) {
485                 a.setFillBefore(fillBefore);
486             }
487             if (repeatModeSet) {
488                 a.setRepeatMode(repeatMode);
489             }
490             if (shareInterpolator) {
491                 a.setInterpolator(interpolator);
492             }
493             if (startOffsetSet) {
494                 long offset = a.getStartOffset();
495                 a.setStartOffset(offset + startOffset);
496                 storedOffsets[i] = offset;
497             }
498             a.initialize(width, height, parentWidth, parentHeight);
499         }
500     }
501 
502     @Override
reset()503     public void reset() {
504         super.reset();
505         restoreChildrenStartOffset();
506     }
507 
508     /**
509      * @hide
510      */
restoreChildrenStartOffset()511     void restoreChildrenStartOffset() {
512         final long[] offsets = mStoredOffsets;
513         if (offsets == null) return;
514 
515         final ArrayList<Animation> children = mAnimations;
516         final int count = children.size();
517 
518         for (int i = 0; i < count; i++) {
519             children.get(i).setStartOffset(offsets[i]);
520         }
521     }
522 
523     /**
524      * @return All the child animations in this AnimationSet. Note that
525      * this may include other AnimationSets, which are not expanded.
526      */
getAnimations()527     public List<Animation> getAnimations() {
528         return mAnimations;
529     }
530 
531     @Override
willChangeTransformationMatrix()532     public boolean willChangeTransformationMatrix() {
533         return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
534     }
535 
536     @Override
willChangeBounds()537     public boolean willChangeBounds() {
538         return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
539     }
540 
541     /** @hide */
542     @Override
hasExtension()543     public boolean hasExtension() {
544         for (Animation animation : mAnimations) {
545             if (animation.hasExtension()) {
546                 return true;
547             }
548         }
549         return false;
550     }
551 }
552