AnimationSet.java revision c861bd778b1b96aa12d3400ec799d80ba38c1f38
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.Animation_duration)) { 93 mFlags |= PROPERTY_DURATION_MASK; 94 } 95 if (a.hasValue(com.android.internal.R.styleable.Animation_fillBefore)) { 96 mFlags |= PROPERTY_FILL_BEFORE_MASK; 97 } 98 if (a.hasValue(com.android.internal.R.styleable.Animation_fillAfter)) { 99 mFlags |= PROPERTY_FILL_AFTER_MASK; 100 } 101 if (a.hasValue(com.android.internal.R.styleable.Animation_repeatMode)) { 102 mFlags |= PROPERTY_REPEAT_MODE_MASK; 103 } 104 if (a.hasValue(com.android.internal.R.styleable.Animation_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 if (changeBounds && a.willChangeTransformationMatrix()) { 228 mFlags |= PROPERTY_CHANGE_BOUNDS_MASK; 229 } 230 231 if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) { 232 mLastEnd = mStartOffset + mDuration; 233 } else { 234 if (mAnimations.size() == 1) { 235 mDuration = a.getStartOffset() + a.getDuration(); 236 mLastEnd = mStartOffset + mDuration; 237 } else { 238 mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration()); 239 mDuration = mLastEnd - mStartOffset; 240 } 241 } 242 243 mDirty = true; 244 } 245 246 /** 247 * Sets the start time of this animation and all child animations 248 * 249 * @see android.view.animation.Animation#setStartTime(long) 250 */ 251 @Override 252 public void setStartTime(long startTimeMillis) { 253 super.setStartTime(startTimeMillis); 254 255 final int count = mAnimations.size(); 256 final ArrayList<Animation> animations = mAnimations; 257 258 for (int i = 0; i < count; i++) { 259 Animation a = animations.get(i); 260 a.setStartTime(startTimeMillis); 261 } 262 } 263 264 @Override 265 public long getStartTime() { 266 long startTime = Long.MAX_VALUE; 267 268 final int count = mAnimations.size(); 269 final ArrayList<Animation> animations = mAnimations; 270 271 for (int i = 0; i < count; i++) { 272 Animation a = animations.get(i); 273 startTime = Math.min(startTime, a.getStartTime()); 274 } 275 276 return startTime; 277 } 278 279 @Override 280 public void restrictDuration(long durationMillis) { 281 super.restrictDuration(durationMillis); 282 283 final ArrayList<Animation> animations = mAnimations; 284 int count = animations.size(); 285 286 for (int i = 0; i < count; i++) { 287 animations.get(i).restrictDuration(durationMillis); 288 } 289 } 290 291 /** 292 * The duration of an AnimationSet is defined to be the 293 * duration of the longest child animation. 294 * 295 * @see android.view.animation.Animation#getDuration() 296 */ 297 @Override 298 public long getDuration() { 299 final ArrayList<Animation> animations = mAnimations; 300 final int count = animations.size(); 301 long duration = 0; 302 303 boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK; 304 if (durationSet) { 305 duration = mDuration; 306 } else { 307 for (int i = 0; i < count; i++) { 308 duration = Math.max(duration, animations.get(i).getDuration()); 309 } 310 } 311 312 return duration; 313 } 314 315 /** 316 * The duration hint of an animation set is the maximum of the duration 317 * hints of all of its component animations. 318 * 319 * @see android.view.animation.Animation#computeDurationHint 320 */ 321 public long computeDurationHint() { 322 long duration = 0; 323 final int count = mAnimations.size(); 324 final ArrayList<Animation> animations = mAnimations; 325 for (int i = count - 1; i >= 0; --i) { 326 final long d = animations.get(i).computeDurationHint(); 327 if (d > duration) duration = d; 328 } 329 return duration; 330 } 331 332 /** 333 * @hide 334 */ 335 public void initializeInvalidateRegion(int left, int top, int right, int bottom) { 336 final RectF region = mPreviousRegion; 337 region.set(left, top, right, bottom); 338 region.inset(-1.0f, -1.0f); 339 340 if (mFillBefore) { 341 final int count = mAnimations.size(); 342 final ArrayList<Animation> animations = mAnimations; 343 final Transformation temp = mTempTransformation; 344 345 final Transformation previousTransformation = mPreviousTransformation; 346 347 for (int i = count - 1; i >= 0; --i) { 348 final Animation a = animations.get(i); 349 350 temp.clear(); 351 final Interpolator interpolator = a.mInterpolator; 352 a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0.0f) 353 : 0.0f, temp); 354 previousTransformation.compose(temp); 355 } 356 } 357 } 358 359 /** 360 * The transformation of an animation set is the concatenation of all of its 361 * component animations. 362 * 363 * @see android.view.animation.Animation#getTransformation 364 */ 365 @Override 366 public boolean getTransformation(long currentTime, Transformation t) { 367 final int count = mAnimations.size(); 368 final ArrayList<Animation> animations = mAnimations; 369 final Transformation temp = mTempTransformation; 370 371 boolean more = false; 372 boolean started = false; 373 boolean ended = true; 374 375 t.clear(); 376 377 for (int i = count - 1; i >= 0; --i) { 378 final Animation a = animations.get(i); 379 380 temp.clear(); 381 more = a.getTransformation(currentTime, temp, getScaleFactor()) || more; 382 t.compose(temp); 383 384 started = started || a.hasStarted(); 385 ended = a.hasEnded() && ended; 386 } 387 388 if (started && !mStarted) { 389 if (mListener != null) { 390 mListener.onAnimationStart(this); 391 } 392 mStarted = true; 393 } 394 395 if (ended != mEnded) { 396 if (mListener != null) { 397 mListener.onAnimationEnd(this); 398 } 399 mEnded = ended; 400 } 401 402 return more; 403 } 404 405 /** 406 * @see android.view.animation.Animation#scaleCurrentDuration(float) 407 */ 408 @Override 409 public void scaleCurrentDuration(float scale) { 410 final ArrayList<Animation> animations = mAnimations; 411 int count = animations.size(); 412 for (int i = 0; i < count; i++) { 413 animations.get(i).scaleCurrentDuration(scale); 414 } 415 } 416 417 /** 418 * @see android.view.animation.Animation#initialize(int, int, int, int) 419 */ 420 @Override 421 public void initialize(int width, int height, int parentWidth, int parentHeight) { 422 super.initialize(width, height, parentWidth, parentHeight); 423 424 boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK; 425 boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK; 426 boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK; 427 boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK; 428 boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK) 429 == PROPERTY_SHARE_INTERPOLATOR_MASK; 430 boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK) 431 == PROPERTY_START_OFFSET_MASK; 432 433 if (shareInterpolator) { 434 ensureInterpolator(); 435 } 436 437 final ArrayList<Animation> children = mAnimations; 438 final int count = children.size(); 439 440 final long duration = mDuration; 441 final boolean fillAfter = mFillAfter; 442 final boolean fillBefore = mFillBefore; 443 final int repeatMode = mRepeatMode; 444 final Interpolator interpolator = mInterpolator; 445 final long startOffset = mStartOffset; 446 447 448 long[] storedOffsets = mStoredOffsets; 449 if (startOffsetSet) { 450 if (storedOffsets == null || storedOffsets.length != count) { 451 storedOffsets = mStoredOffsets = new long[count]; 452 } 453 } else if (storedOffsets != null) { 454 storedOffsets = mStoredOffsets = null; 455 } 456 457 for (int i = 0; i < count; i++) { 458 Animation a = children.get(i); 459 if (durationSet) { 460 a.setDuration(duration); 461 } 462 if (fillAfterSet) { 463 a.setFillAfter(fillAfter); 464 } 465 if (fillBeforeSet) { 466 a.setFillBefore(fillBefore); 467 } 468 if (repeatModeSet) { 469 a.setRepeatMode(repeatMode); 470 } 471 if (shareInterpolator) { 472 a.setInterpolator(interpolator); 473 } 474 if (startOffsetSet) { 475 long offset = a.getStartOffset(); 476 a.setStartOffset(offset + startOffset); 477 storedOffsets[i] = offset; 478 } 479 a.initialize(width, height, parentWidth, parentHeight); 480 } 481 } 482 483 @Override 484 public void reset() { 485 super.reset(); 486 restoreChildrenStartOffset(); 487 } 488 489 /** 490 * @hide 491 */ 492 void restoreChildrenStartOffset() { 493 final long[] offsets = mStoredOffsets; 494 if (offsets == null) return; 495 496 final ArrayList<Animation> children = mAnimations; 497 final int count = children.size(); 498 499 for (int i = 0; i < count; i++) { 500 children.get(i).setStartOffset(offsets[i]); 501 } 502 } 503 504 /** 505 * @return All the child animations in this AnimationSet. Note that 506 * this may include other AnimationSets, which are not expanded. 507 */ 508 public List<Animation> getAnimations() { 509 return mAnimations; 510 } 511 512 @Override 513 public boolean willChangeTransformationMatrix() { 514 return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK; 515 } 516 517 @Override 518 public boolean willChangeBounds() { 519 return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK; 520 } 521} 522