AnimationSet.java revision c861bd778b1b96aa12d3400ec799d80ba38c1f38
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.Animation_duration)) {
93                mFlags |= PROPERTY_DURATION_MASK;
94            }
95            if (a.hasValue(com.android.internal.R.styleable.Animation_fillBefore)) {
96                mFlags |= PROPERTY_FILL_BEFORE_MASK;
97            }
98            if (a.hasValue(com.android.internal.R.styleable.Animation_fillAfter)) {
99                mFlags |= PROPERTY_FILL_AFTER_MASK;
100            }
101            if (a.hasValue(com.android.internal.R.styleable.Animation_repeatMode)) {
102                mFlags |= PROPERTY_REPEAT_MODE_MASK;
103            }
104            if (a.hasValue(com.android.internal.R.styleable.Animation_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        if (changeBounds && a.willChangeTransformationMatrix()) {
228            mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
229        }
230
231        if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) {
232            mLastEnd = mStartOffset + mDuration;
233        } else {
234            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            }
241        }
242
243        mDirty = true;
244    }
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     */
335    public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
336        final RectF region = mPreviousRegion;
337        region.set(left, top, right, bottom);
338        region.inset(-1.0f, -1.0f);
339
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();
351                final Interpolator interpolator = a.mInterpolator;
352                a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0.0f)
353                        : 0.0f, temp);
354                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();
381            more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
382            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;
449        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;
455        }
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
499        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}
522