AnimationSet.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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.util.AttributeSet;
22import android.graphics.RectF;
23
24import java.util.ArrayList;
25import java.util.List;
26
27/**
28 * Represents a group of Animations that should be played together.
29 * The transformation of each individual animation are composed
30 * together into a single transform.
31 * If AnimationSet sets any properties that its children also set
32 * (for example, duration or fillBefore), the values of AnimationSet
33 * override the child values.
34 */
35public class AnimationSet extends Animation {
36    private static final int PROPERTY_FILL_AFTER_MASK         = 0x1;
37    private static final int PROPERTY_FILL_BEFORE_MASK        = 0x2;
38    private static final int PROPERTY_REPEAT_MODE_MASK        = 0x4;
39    private static final int PROPERTY_START_OFFSET_MASK       = 0x8;
40    private static final int PROPERTY_SHARE_INTERPOLATOR_MASK = 0x10;
41    private static final int PROPERTY_DURATION_MASK           = 0x20;
42    private static final int PROPERTY_MORPH_MATRIX_MASK       = 0x40;
43    private static final int PROPERTY_CHANGE_BOUNDS_MASK      = 0x80;
44
45    private int mFlags = 0;
46
47    private ArrayList<Animation> mAnimations = new ArrayList<Animation>();
48
49    private Transformation mTempTransformation = new Transformation();
50
51    private long mLastEnd;
52
53    private long[] mStoredOffsets;
54
55    /**
56     * Constructor used whan an AnimationSet is loaded from a resource.
57     *
58     * @param context Application context to use
59     * @param attrs Attribute set from which to read values
60     */
61    public AnimationSet(Context context, AttributeSet attrs) {
62        super(context, attrs);
63
64        TypedArray a =
65            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimationSet);
66
67        setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK,
68                a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true));
69        init();
70
71        a.recycle();
72    }
73
74
75    /**
76     * Constructor to use when building an AnimationSet from code
77     *
78     * @param shareInterpolator Pass true if all of the animations in this set
79     *        should use the interpolator assocciated with this AnimationSet.
80     *        Pass false if each animation should use its own interpolator.
81     */
82    public AnimationSet(boolean shareInterpolator) {
83        setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator);
84        init();
85    }
86
87    @Override
88    protected AnimationSet clone() throws CloneNotSupportedException {
89        final AnimationSet animation = (AnimationSet) super.clone();
90        animation.mTempTransformation = new Transformation();
91        animation.mAnimations = new ArrayList<Animation>();
92
93        final int count = mAnimations.size();
94        final ArrayList<Animation> animations = mAnimations;
95
96        for (int i = 0; i < count; i++) {
97            animation.mAnimations.add(animations.get(i).clone());
98        }
99
100        return animation;
101    }
102
103    private void setFlag(int mask, boolean value) {
104        if (value) {
105            mFlags |= mask;
106        } else {
107            mFlags &= ~mask;
108        }
109    }
110
111    private void init() {
112        mStartTime = 0;
113        mDuration = 0;
114    }
115
116    @Override
117    public void setFillAfter(boolean fillAfter) {
118        mFlags |= PROPERTY_FILL_AFTER_MASK;
119        super.setFillAfter(fillAfter);
120    }
121
122    @Override
123    public void setFillBefore(boolean fillBefore) {
124        mFlags |= PROPERTY_FILL_BEFORE_MASK;
125        super.setFillBefore(fillBefore);
126    }
127
128    @Override
129    public void setRepeatMode(int repeatMode) {
130        mFlags |= PROPERTY_REPEAT_MODE_MASK;
131        super.setRepeatMode(repeatMode);
132    }
133
134    @Override
135    public void setStartOffset(long startOffset) {
136        mFlags |= PROPERTY_START_OFFSET_MASK;
137        super.setStartOffset(startOffset);
138    }
139
140    /**
141     * <p>Sets the duration of every child animation.</p>
142     *
143     * @param durationMillis the duration of the animation, in milliseconds, for
144     *        every child in this set
145     */
146    @Override
147    public void setDuration(long durationMillis) {
148        mFlags |= PROPERTY_DURATION_MASK;
149        super.setDuration(durationMillis);
150    }
151
152    /**
153     * Add a child animation to this animation set.
154     * The transforms of the child animations are applied in the order
155     * that they were added
156     * @param a Animation to add.
157     */
158    public void addAnimation(Animation a) {
159        mAnimations.add(a);
160
161        boolean noMatrix = (mFlags & PROPERTY_MORPH_MATRIX_MASK) == 0;
162        if (noMatrix && a.willChangeTransformationMatrix()) {
163            mFlags |= PROPERTY_MORPH_MATRIX_MASK;
164        }
165
166        boolean changeBounds = (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == 0;
167        if (changeBounds && a.willChangeTransformationMatrix()) {
168            mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
169        }
170
171        if (mAnimations.size() == 1) {
172            mDuration = a.getStartOffset() + a.getDuration();
173            mLastEnd = mStartOffset + mDuration;
174        } else {
175            mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration());
176            mDuration = mLastEnd - mStartOffset;
177        }
178    }
179
180    /**
181     * Sets the start time of this animation and all child animations
182     *
183     * @see android.view.animation.Animation#setStartTime(long)
184     */
185    @Override
186    public void setStartTime(long startTimeMillis) {
187        super.setStartTime(startTimeMillis);
188
189        final int count = mAnimations.size();
190        final ArrayList<Animation> animations = mAnimations;
191
192        for (int i = 0; i < count; i++) {
193            Animation a = animations.get(i);
194            a.setStartTime(startTimeMillis);
195        }
196    }
197
198    @Override
199    public long getStartTime() {
200        long startTime = Long.MAX_VALUE;
201
202        final int count = mAnimations.size();
203        final ArrayList<Animation> animations = mAnimations;
204
205        for (int i = 0; i < count; i++) {
206            Animation a = animations.get(i);
207            startTime = Math.min(startTime, a.getStartTime());
208        }
209
210        return startTime;
211    }
212
213    @Override
214    public void restrictDuration(long durationMillis) {
215        super.restrictDuration(durationMillis);
216
217        final ArrayList<Animation> animations = mAnimations;
218        int count = animations.size();
219
220        for (int i = 0; i < count; i++) {
221            animations.get(i).restrictDuration(durationMillis);
222        }
223    }
224
225    /**
226     * The duration of an AnimationSet is defined to be the
227     * duration of the longest child animation.
228     *
229     * @see android.view.animation.Animation#getDuration()
230     */
231    @Override
232    public long getDuration() {
233        final ArrayList<Animation> animations = mAnimations;
234        final int count = animations.size();
235        long duration = 0;
236
237        boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
238        if (durationSet) {
239            duration = mDuration;
240        } else {
241            for (int i = 0; i < count; i++) {
242                duration = Math.max(duration, animations.get(i).getDuration());
243            }
244        }
245
246        return duration;
247    }
248
249    /**
250     * The duration hint of an animation set is the maximum of the duration
251     * hints of all of its component animations.
252     *
253     * @see android.view.animation.Animation#computeDurationHint
254     */
255    public long computeDurationHint() {
256        long duration = 0;
257        final int count = mAnimations.size();
258        final ArrayList<Animation> animations = mAnimations;
259        for (int i = count - 1; i >= 0; --i) {
260            final long d = animations.get(i).computeDurationHint();
261            if (d > duration) duration = d;
262        }
263        return duration;
264    }
265
266    /**
267     * @hide
268     */
269    public void getInvalidateRegion(int left, int top, int right, int bottom,
270            RectF invalidate, Transformation transformation) {
271
272        final RectF previousRegion = mPreviousRegion;
273
274        invalidate.set(left, top, right, bottom);
275        transformation.getMatrix().mapRect(invalidate);
276        invalidate.union(previousRegion);
277
278        previousRegion.set(left, top, right, bottom);
279        transformation.getMatrix().mapRect(previousRegion);
280
281        final Transformation tempTransformation = mTransformation;
282        final Transformation previousTransformation = mPreviousTransformation;
283
284        tempTransformation.set(transformation);
285        transformation.set(previousTransformation);
286        previousTransformation.set(tempTransformation);
287    }
288
289    /**
290     * @hide
291     */
292    public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
293        final RectF region = mPreviousRegion;
294        region.set(left, top, right, bottom);
295
296        if (mFillBefore) {
297            final int count = mAnimations.size();
298            final ArrayList<Animation> animations = mAnimations;
299            final Transformation temp = mTempTransformation;
300
301            final Transformation previousTransformation = mPreviousTransformation;
302
303            for (int i = count - 1; i >= 0; --i) {
304                final Animation a = animations.get(i);
305
306                temp.clear();
307                a.applyTransformation(0.0f, temp);
308                previousTransformation.compose(temp);
309            }
310        }
311    }
312
313    /**
314     * The transformation of an animation set is the concatenation of all of its
315     * component animations.
316     *
317     * @see android.view.animation.Animation#getTransformation
318     */
319    @Override
320    public boolean getTransformation(long currentTime, Transformation t) {
321        final int count = mAnimations.size();
322        final ArrayList<Animation> animations = mAnimations;
323        final Transformation temp = mTempTransformation;
324
325        boolean more = false;
326        boolean started = false;
327        boolean ended = true;
328
329        t.clear();
330
331        for (int i = count - 1; i >= 0; --i) {
332            final Animation a = animations.get(i);
333
334            temp.clear();
335            more = a.getTransformation(currentTime, temp) || more;
336            t.compose(temp);
337
338            started = started || a.hasStarted();
339            ended = a.hasEnded() && ended;
340        }
341
342        if (started && !mStarted) {
343            if (mListener != null) {
344                mListener.onAnimationStart(this);
345            }
346            mStarted = true;
347        }
348
349        if (ended != mEnded) {
350            if (mListener != null) {
351                mListener.onAnimationEnd(this);
352            }
353            mEnded = ended;
354        }
355
356        return more;
357    }
358
359    /**
360     * @see android.view.animation.Animation#scaleCurrentDuration(float)
361     */
362    @Override
363    public void scaleCurrentDuration(float scale) {
364        final ArrayList<Animation> animations = mAnimations;
365        int count = animations.size();
366        for (int i = 0; i < count; i++) {
367            animations.get(i).scaleCurrentDuration(scale);
368        }
369    }
370
371    /**
372     * @see android.view.animation.Animation#initialize(int, int, int, int)
373     */
374    @Override
375    public void initialize(int width, int height, int parentWidth, int parentHeight) {
376        super.initialize(width, height, parentWidth, parentHeight);
377
378        boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
379        boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK;
380        boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK;
381        boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK;
382        boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK)
383                == PROPERTY_SHARE_INTERPOLATOR_MASK;
384        boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
385                == PROPERTY_START_OFFSET_MASK;
386
387        if (shareInterpolator) {
388            ensureInterpolator();
389        }
390
391        final ArrayList<Animation> children = mAnimations;
392        final int count = children.size();
393
394        final long duration = mDuration;
395        final boolean fillAfter = mFillAfter;
396        final boolean fillBefore = mFillBefore;
397        final int repeatMode = mRepeatMode;
398        final Interpolator interpolator = mInterpolator;
399        final long startOffset = mStartOffset;
400
401
402        long[] storedOffsets = mStoredOffsets;
403        if (storedOffsets == null || storedOffsets.length != count) {
404            storedOffsets = mStoredOffsets = new long[count];
405        }
406
407        for (int i = 0; i < count; i++) {
408            Animation a = children.get(i);
409            if (durationSet) {
410                a.setDuration(duration);
411            }
412            if (fillAfterSet) {
413                a.setFillAfter(fillAfter);
414            }
415            if (fillBeforeSet) {
416                a.setFillBefore(fillBefore);
417            }
418            if (repeatModeSet) {
419                a.setRepeatMode(repeatMode);
420            }
421            if (shareInterpolator) {
422                a.setInterpolator(interpolator);
423            }
424            if (startOffsetSet) {
425                long offset = a.getStartOffset();
426                a.setStartOffset(offset + startOffset);
427                storedOffsets[i] = offset;
428            }
429            a.initialize(width, height, parentWidth, parentHeight);
430        }
431    }
432
433    @Override
434    public void reset() {
435        super.reset();
436        restoreChildrenStartOffset();
437    }
438
439    /**
440     * @hide
441     */
442    void restoreChildrenStartOffset() {
443        final long[] offsets = mStoredOffsets;
444        if (offsets == null) return;
445
446        final ArrayList<Animation> children = mAnimations;
447        final int count = children.size();
448
449
450        for (int i = 0; i < count; i++) {
451            children.get(i).setStartOffset(offsets[i]);
452        }
453    }
454
455    /**
456     * @return All the child animations in this AnimationSet. Note that
457     * this may include other AnimationSets, which are not expanded.
458     */
459    public List<Animation> getAnimations() {
460        return mAnimations;
461    }
462
463    @Override
464    public boolean willChangeTransformationMatrix() {
465        return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
466    }
467
468    @Override
469    public boolean willChangeBounds() {
470        return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
471    }
472}
473