Home | History | Annotate | Download | only in animation
      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      */
     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      */
    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, 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
    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