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 */ AnimationSet(Context context, AttributeSet attrs)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 */ AnimationSet(boolean shareInterpolator)120 public AnimationSet(boolean shareInterpolator) { 121 setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator); 122 init(); 123 } 124 125 @Override clone()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 setFlag(int mask, boolean value)141 private void setFlag(int mask, boolean value) { 142 if (value) { 143 mFlags |= mask; 144 } else { 145 mFlags &= ~mask; 146 } 147 } 148 init()149 private void init() { 150 mStartTime = 0; 151 } 152 153 @Override setFillAfter(boolean fillAfter)154 public void setFillAfter(boolean fillAfter) { 155 mFlags |= PROPERTY_FILL_AFTER_MASK; 156 super.setFillAfter(fillAfter); 157 } 158 159 @Override setFillBefore(boolean fillBefore)160 public void setFillBefore(boolean fillBefore) { 161 mFlags |= PROPERTY_FILL_BEFORE_MASK; 162 super.setFillBefore(fillBefore); 163 } 164 165 @Override setRepeatMode(int repeatMode)166 public void setRepeatMode(int repeatMode) { 167 mFlags |= PROPERTY_REPEAT_MODE_MASK; 168 super.setRepeatMode(repeatMode); 169 } 170 171 @Override setStartOffset(long startOffset)172 public void setStartOffset(long startOffset) { 173 mFlags |= PROPERTY_START_OFFSET_MASK; 174 super.setStartOffset(startOffset); 175 } 176 177 /** 178 * @hide 179 */ 180 @Override hasAlpha()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 setDuration(long durationMillis)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 */ addAnimation(Animation a)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 setStartTime(long startTimeMillis)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 getStartTime()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 restrictDuration(long durationMillis)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 getDuration()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 */ computeDurationHint()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 */ initializeInvalidateRegion(int left, int top, int right, int bottom)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 getTransformation(long currentTime, Transformation t)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 dispatchAnimationStart(); 393 mStarted = true; 394 } 395 396 if (ended != mEnded) { 397 dispatchAnimationEnd(); 398 mEnded = ended; 399 } 400 401 return more; 402 } 403 404 /** 405 * @see android.view.animation.Animation#scaleCurrentDuration(float) 406 */ 407 @Override scaleCurrentDuration(float scale)408 public void scaleCurrentDuration(float scale) { 409 final ArrayList<Animation> animations = mAnimations; 410 int count = animations.size(); 411 for (int i = 0; i < count; i++) { 412 animations.get(i).scaleCurrentDuration(scale); 413 } 414 } 415 416 /** 417 * @see android.view.animation.Animation#initialize(int, int, int, int) 418 */ 419 @Override initialize(int width, int height, int parentWidth, int parentHeight)420 public void initialize(int width, int height, int parentWidth, int parentHeight) { 421 super.initialize(width, height, parentWidth, parentHeight); 422 423 boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK; 424 boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK; 425 boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK; 426 boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK; 427 boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK) 428 == PROPERTY_SHARE_INTERPOLATOR_MASK; 429 boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK) 430 == PROPERTY_START_OFFSET_MASK; 431 432 if (shareInterpolator) { 433 ensureInterpolator(); 434 } 435 436 final ArrayList<Animation> children = mAnimations; 437 final int count = children.size(); 438 439 final long duration = mDuration; 440 final boolean fillAfter = mFillAfter; 441 final boolean fillBefore = mFillBefore; 442 final int repeatMode = mRepeatMode; 443 final Interpolator interpolator = mInterpolator; 444 final long startOffset = mStartOffset; 445 446 447 long[] storedOffsets = mStoredOffsets; 448 if (startOffsetSet) { 449 if (storedOffsets == null || storedOffsets.length != count) { 450 storedOffsets = mStoredOffsets = new long[count]; 451 } 452 } else if (storedOffsets != null) { 453 storedOffsets = mStoredOffsets = null; 454 } 455 456 for (int i = 0; i < count; i++) { 457 Animation a = children.get(i); 458 if (durationSet) { 459 a.setDuration(duration); 460 } 461 if (fillAfterSet) { 462 a.setFillAfter(fillAfter); 463 } 464 if (fillBeforeSet) { 465 a.setFillBefore(fillBefore); 466 } 467 if (repeatModeSet) { 468 a.setRepeatMode(repeatMode); 469 } 470 if (shareInterpolator) { 471 a.setInterpolator(interpolator); 472 } 473 if (startOffsetSet) { 474 long offset = a.getStartOffset(); 475 a.setStartOffset(offset + startOffset); 476 storedOffsets[i] = offset; 477 } 478 a.initialize(width, height, parentWidth, parentHeight); 479 } 480 } 481 482 @Override reset()483 public void reset() { 484 super.reset(); 485 restoreChildrenStartOffset(); 486 } 487 488 /** 489 * @hide 490 */ restoreChildrenStartOffset()491 void restoreChildrenStartOffset() { 492 final long[] offsets = mStoredOffsets; 493 if (offsets == null) return; 494 495 final ArrayList<Animation> children = mAnimations; 496 final int count = children.size(); 497 498 for (int i = 0; i < count; i++) { 499 children.get(i).setStartOffset(offsets[i]); 500 } 501 } 502 503 /** 504 * @return All the child animations in this AnimationSet. Note that 505 * this may include other AnimationSets, which are not expanded. 506 */ getAnimations()507 public List<Animation> getAnimations() { 508 return mAnimations; 509 } 510 511 @Override willChangeTransformationMatrix()512 public boolean willChangeTransformationMatrix() { 513 return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK; 514 } 515 516 @Override willChangeBounds()517 public boolean willChangeBounds() { 518 return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK; 519 } 520 } 521