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