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