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#getTransformation
367      */
368     @Override
getTransformation(long currentTime, Transformation t)369     public boolean getTransformation(long currentTime, Transformation t) {
370         final int count = mAnimations.size();
371         final ArrayList<Animation> animations = mAnimations;
372         final Transformation temp = mTempTransformation;
373 
374         boolean more = false;
375         boolean started = false;
376         boolean ended = true;
377 
378         t.clear();
379 
380         for (int i = count - 1; i >= 0; --i) {
381             final Animation a = animations.get(i);
382 
383             temp.clear();
384             more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
385             t.compose(temp);
386 
387             started = started || a.hasStarted();
388             ended = a.hasEnded() && ended;
389         }
390 
391         if (started && !mStarted) {
392             dispatchAnimationStart();
393             mStarted = true;
394         }
395 
396         if (ended != mEnded) {
397             dispatchAnimationEnd();
398             mEnded = ended;
399         }
400 
401         return more;
402     }
403 
404     /**
405      * @see android.view.animation.Animation#scaleCurrentDuration(float)
406      */
407     @Override
scaleCurrentDuration(float scale)408     public void scaleCurrentDuration(float scale) {
409         final ArrayList<Animation> animations = mAnimations;
410         int count = animations.size();
411         for (int i = 0; i < count; i++) {
412             animations.get(i).scaleCurrentDuration(scale);
413         }
414     }
415 
416     /**
417      * @see android.view.animation.Animation#initialize(int, int, int, int)
418      */
419     @Override
initialize(int width, int height, int parentWidth, int parentHeight)420     public void initialize(int width, int height, int parentWidth, int parentHeight) {
421         super.initialize(width, height, parentWidth, parentHeight);
422 
423         boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
424         boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK;
425         boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK;
426         boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK;
427         boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK)
428                 == PROPERTY_SHARE_INTERPOLATOR_MASK;
429         boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
430                 == PROPERTY_START_OFFSET_MASK;
431 
432         if (shareInterpolator) {
433             ensureInterpolator();
434         }
435 
436         final ArrayList<Animation> children = mAnimations;
437         final int count = children.size();
438 
439         final long duration = mDuration;
440         final boolean fillAfter = mFillAfter;
441         final boolean fillBefore = mFillBefore;
442         final int repeatMode = mRepeatMode;
443         final Interpolator interpolator = mInterpolator;
444         final long startOffset = mStartOffset;
445 
446 
447         long[] storedOffsets = mStoredOffsets;
448         if (startOffsetSet) {
449             if (storedOffsets == null || storedOffsets.length != count) {
450                 storedOffsets = mStoredOffsets = new long[count];
451             }
452         } else if (storedOffsets != null) {
453             storedOffsets = mStoredOffsets = null;
454         }
455 
456         for (int i = 0; i < count; i++) {
457             Animation a = children.get(i);
458             if (durationSet) {
459                 a.setDuration(duration);
460             }
461             if (fillAfterSet) {
462                 a.setFillAfter(fillAfter);
463             }
464             if (fillBeforeSet) {
465                 a.setFillBefore(fillBefore);
466             }
467             if (repeatModeSet) {
468                 a.setRepeatMode(repeatMode);
469             }
470             if (shareInterpolator) {
471                 a.setInterpolator(interpolator);
472             }
473             if (startOffsetSet) {
474                 long offset = a.getStartOffset();
475                 a.setStartOffset(offset + startOffset);
476                 storedOffsets[i] = offset;
477             }
478             a.initialize(width, height, parentWidth, parentHeight);
479         }
480     }
481 
482     @Override
reset()483     public void reset() {
484         super.reset();
485         restoreChildrenStartOffset();
486     }
487 
488     /**
489      * @hide
490      */
restoreChildrenStartOffset()491     void restoreChildrenStartOffset() {
492         final long[] offsets = mStoredOffsets;
493         if (offsets == null) return;
494 
495         final ArrayList<Animation> children = mAnimations;
496         final int count = children.size();
497 
498         for (int i = 0; i < count; i++) {
499             children.get(i).setStartOffset(offsets[i]);
500         }
501     }
502 
503     /**
504      * @return All the child animations in this AnimationSet. Note that
505      * this may include other AnimationSets, which are not expanded.
506      */
getAnimations()507     public List<Animation> getAnimations() {
508         return mAnimations;
509     }
510 
511     @Override
willChangeTransformationMatrix()512     public boolean willChangeTransformationMatrix() {
513         return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
514     }
515 
516     @Override
willChangeBounds()517     public boolean willChangeBounds() {
518         return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
519     }
520 }
521