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