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