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
17package android.view.animation;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.os.Build;
22import 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.
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 */
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;
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     */
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 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;
151    }
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
177    /**
178     * @hide
179     */
180    @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
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
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     */
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, 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
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
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
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
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     */
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     */
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
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            if (mListener != null) {
393                mListener.onAnimationStart(this);
394            }
395            mStarted = true;
396        }
397
398        if (ended != mEnded) {
399            if (mListener != null) {
400                mListener.onAnimationEnd(this);
401            }
402            mEnded = ended;
403        }
404
405        return more;
406    }
407
408    /**
409     * @see android.view.animation.Animation#scaleCurrentDuration(float)
410     */
411    @Override
412    public void scaleCurrentDuration(float scale) {
413        final ArrayList<Animation> animations = mAnimations;
414        int count = animations.size();
415        for (int i = 0; i < count; i++) {
416            animations.get(i).scaleCurrentDuration(scale);
417        }
418    }
419
420    /**
421     * @see android.view.animation.Animation#initialize(int, int, int, int)
422     */
423    @Override
424    public void initialize(int width, int height, int parentWidth, int parentHeight) {
425        super.initialize(width, height, parentWidth, parentHeight);
426
427        boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
428        boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK;
429        boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK;
430        boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK;
431        boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK)
432                == PROPERTY_SHARE_INTERPOLATOR_MASK;
433        boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
434                == PROPERTY_START_OFFSET_MASK;
435
436        if (shareInterpolator) {
437            ensureInterpolator();
438        }
439
440        final ArrayList<Animation> children = mAnimations;
441        final int count = children.size();
442
443        final long duration = mDuration;
444        final boolean fillAfter = mFillAfter;
445        final boolean fillBefore = mFillBefore;
446        final int repeatMode = mRepeatMode;
447        final Interpolator interpolator = mInterpolator;
448        final long startOffset = mStartOffset;
449
450
451        long[] storedOffsets = mStoredOffsets;
452        if (startOffsetSet) {
453            if (storedOffsets == null || storedOffsets.length != count) {
454                storedOffsets = mStoredOffsets = new long[count];
455            }
456        } else if (storedOffsets != null) {
457            storedOffsets = mStoredOffsets = null;
458        }
459
460        for (int i = 0; i < count; i++) {
461            Animation a = children.get(i);
462            if (durationSet) {
463                a.setDuration(duration);
464            }
465            if (fillAfterSet) {
466                a.setFillAfter(fillAfter);
467            }
468            if (fillBeforeSet) {
469                a.setFillBefore(fillBefore);
470            }
471            if (repeatModeSet) {
472                a.setRepeatMode(repeatMode);
473            }
474            if (shareInterpolator) {
475                a.setInterpolator(interpolator);
476            }
477            if (startOffsetSet) {
478                long offset = a.getStartOffset();
479                a.setStartOffset(offset + startOffset);
480                storedOffsets[i] = offset;
481            }
482            a.initialize(width, height, parentWidth, parentHeight);
483        }
484    }
485
486    @Override
487    public void reset() {
488        super.reset();
489        restoreChildrenStartOffset();
490    }
491
492    /**
493     * @hide
494     */
495    void restoreChildrenStartOffset() {
496        final long[] offsets = mStoredOffsets;
497        if (offsets == null) return;
498
499        final ArrayList<Animation> children = mAnimations;
500        final int count = children.size();
501
502        for (int i = 0; i < count; i++) {
503            children.get(i).setStartOffset(offsets[i]);
504        }
505    }
506
507    /**
508     * @return All the child animations in this AnimationSet. Note that
509     * this may include other AnimationSets, which are not expanded.
510     */
511    public List<Animation> getAnimations() {
512        return mAnimations;
513    }
514
515    @Override
516    public boolean willChangeTransformationMatrix() {
517        return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
518    }
519
520    @Override
521    public boolean willChangeBounds() {
522        return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
523    }
524}
525