StackStateAnimator.java revision 1efb240c1a0aeca9492cf8891794712adfdb1fa7
1/* 2 * Copyright (C) 2014 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 com.android.systemui.statusbar.stack; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.PropertyValuesHolder; 23import android.animation.ValueAnimator; 24import android.view.View; 25import android.view.animation.AnimationUtils; 26import android.view.animation.Interpolator; 27 28import com.android.systemui.R; 29import com.android.systemui.statusbar.ExpandableView; 30 31import java.util.ArrayList; 32import java.util.HashSet; 33import java.util.Set; 34import java.util.Stack; 35 36/** 37 * An stack state animator which handles animations to new StackScrollStates 38 */ 39public class StackStateAnimator { 40 41 public static final int ANIMATION_DURATION_STANDARD = 360; 42 public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; 43 public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; 44 public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80; 45 public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32; 46 private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; 47 48 private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; 49 private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; 50 private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag; 51 private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag; 52 private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag; 53 private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag; 54 private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag; 55 private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag; 56 private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag; 57 private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag; 58 private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag; 59 private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag; 60 private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag; 61 private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag; 62 private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag; 63 private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag; 64 private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag; 65 private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag; 66 67 private final Interpolator mFastOutSlowInInterpolator; 68 public NotificationStackScrollLayout mHostLayout; 69 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents = 70 new ArrayList<>(); 71 private ArrayList<View> mNewAddChildren = new ArrayList<>(); 72 private Set<Animator> mAnimatorSet = new HashSet<Animator>(); 73 private Stack<AnimatorListenerAdapter> mAnimationListenerPool 74 = new Stack<AnimatorListenerAdapter>(); 75 private AnimationFilter mAnimationFilter = new AnimationFilter(); 76 private long mCurrentLength; 77 78 private ValueAnimator mTopOverScrollAnimator; 79 private ValueAnimator mBottomOverScrollAnimator; 80 81 public StackStateAnimator(NotificationStackScrollLayout hostLayout) { 82 mHostLayout = hostLayout; 83 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(), 84 android.R.interpolator.fast_out_slow_in); 85 } 86 87 public boolean isRunning() { 88 return !mAnimatorSet.isEmpty(); 89 } 90 91 public void startAnimationForEvents( 92 ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents, 93 StackScrollState finalState) { 94 95 processAnimationEvents(mAnimationEvents, finalState); 96 97 int childCount = mHostLayout.getChildCount(); 98 mAnimationFilter.applyCombination(mNewEvents); 99 mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents); 100 for (int i = 0; i < childCount; i++) { 101 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); 102 103 StackScrollState.ViewState viewState = finalState.getViewStateForView(child); 104 if (viewState == null || child.getVisibility() == View.GONE) { 105 continue; 106 } 107 108 child.setClipBounds(null); 109 startAnimations(child, viewState, finalState); 110 } 111 if (!isRunning()) { 112 // no child has preformed any animation, lets finish 113 onAnimationFinished(); 114 } 115 mNewEvents.clear(); 116 mNewAddChildren.clear(); 117 } 118 119 /** 120 * Start an animation to the given viewState 121 */ 122 private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState, 123 StackScrollState finalState) { 124 int childVisibility = child.getVisibility(); 125 boolean wasVisible = childVisibility == View.VISIBLE; 126 final float alpha = viewState.alpha; 127 if (!wasVisible && alpha != 0 && !viewState.gone) { 128 child.setVisibility(View.VISIBLE); 129 } 130 131 boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation; 132 boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation; 133 boolean scaleChanging = child.getScaleX() != viewState.scale; 134 boolean alphaChanging = alpha != child.getAlpha(); 135 boolean heightChanging = viewState.height != child.getActualHeight(); 136 boolean wasAdded = mNewAddChildren.contains(child); 137 boolean hasDelays = mAnimationFilter.hasDelays; 138 boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging || 139 alphaChanging || heightChanging; 140 long delay = 0; 141 if (hasDelays && isDelayRelevant || wasAdded) { 142 delay = calculateChildAnimationDelay(viewState, finalState); 143 } 144 145 // start translationY animation 146 if (yTranslationChanging) { 147 startYTranslationAnimation(child, viewState, delay); 148 } 149 150 // start translationZ animation 151 if (zTranslationChanging) { 152 startZTranslationAnimation(child, viewState, delay); 153 } 154 155 // start scale animation 156 if (scaleChanging) { 157 startScaleAnimation(child, viewState); 158 } 159 160 // start alpha animation 161 if (alphaChanging) { 162 startAlphaAnimation(child, viewState, delay); 163 } 164 165 // start height animation 166 if (heightChanging) { 167 startHeightAnimation(child, viewState, delay); 168 } 169 170 // start dimmed animation 171 child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); 172 173 if (wasAdded) { 174 child.performAddAnimation(delay); 175 } 176 } 177 178 private long calculateChildAnimationDelay(StackScrollState.ViewState viewState, 179 StackScrollState finalState) { 180 long minDelay = 0; 181 for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) { 182 long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING; 183 switch (event.animationType) { 184 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: { 185 int ownIndex = viewState.notGoneIndex; 186 int changingIndex = finalState 187 .getViewStateForView(event.changingView).notGoneIndex; 188 int difference = Math.abs(ownIndex - changingIndex); 189 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE, 190 difference - 1)); 191 long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement; 192 minDelay = Math.max(delay, minDelay); 193 break; 194 } 195 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT: 196 delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL; 197 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: { 198 int ownIndex = viewState.notGoneIndex; 199 boolean noNextView = event.viewAfterChangingView == null; 200 View viewAfterChangingView = noNextView 201 ? mHostLayout.getLastChildNotGone() 202 : event.viewAfterChangingView; 203 204 int nextIndex = finalState 205 .getViewStateForView(viewAfterChangingView).notGoneIndex; 206 if (ownIndex >= nextIndex) { 207 // we only have the view afterwards 208 ownIndex++; 209 } 210 int difference = Math.abs(ownIndex - nextIndex); 211 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE, 212 difference - 1)); 213 long delay = difference * delayPerElement; 214 minDelay = Math.max(delay, minDelay); 215 break; 216 } 217 default: 218 break; 219 } 220 } 221 return minDelay; 222 } 223 224 private void startHeightAnimation(final ExpandableView child, 225 StackScrollState.ViewState viewState, long delay) { 226 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT); 227 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); 228 int newEndValue = viewState.height; 229 if (previousEndValue != null && previousEndValue == newEndValue) { 230 return; 231 } 232 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT); 233 if (!mAnimationFilter.animateHeight) { 234 // just a local update was performed 235 if (previousAnimator != null) { 236 // we need to increase all animation keyframes of the previous animator by the 237 // relative change to the end value 238 PropertyValuesHolder[] values = previousAnimator.getValues(); 239 int relativeDiff = newEndValue - previousEndValue; 240 int newStartValue = previousStartValue + relativeDiff; 241 values[0].setIntValues(newStartValue, newEndValue); 242 child.setTag(TAG_START_HEIGHT, newStartValue); 243 child.setTag(TAG_END_HEIGHT, newEndValue); 244 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 245 return; 246 } else { 247 // no new animation needed, let's just apply the value 248 child.setActualHeight(newEndValue, false); 249 return; 250 } 251 } 252 253 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue); 254 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 255 @Override 256 public void onAnimationUpdate(ValueAnimator animation) { 257 child.setActualHeight((int) animation.getAnimatedValue(), 258 false /* notifyListeners */); 259 } 260 }); 261 animator.setInterpolator(mFastOutSlowInInterpolator); 262 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 263 animator.setDuration(newDuration); 264 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { 265 animator.setStartDelay(delay); 266 } 267 animator.addListener(getGlobalAnimationFinishedListener()); 268 // remove the tag when the animation is finished 269 animator.addListener(new AnimatorListenerAdapter() { 270 @Override 271 public void onAnimationEnd(Animator animation) { 272 child.setTag(TAG_ANIMATOR_HEIGHT, null); 273 child.setTag(TAG_START_HEIGHT, null); 274 child.setTag(TAG_END_HEIGHT, null); 275 } 276 }); 277 startAnimator(animator); 278 child.setTag(TAG_ANIMATOR_HEIGHT, animator); 279 child.setTag(TAG_START_HEIGHT, child.getActualHeight()); 280 child.setTag(TAG_END_HEIGHT, newEndValue); 281 } 282 283 private void startAlphaAnimation(final ExpandableView child, 284 final StackScrollState.ViewState viewState, long delay) { 285 Float previousStartValue = getChildTag(child,TAG_START_ALPHA); 286 Float previousEndValue = getChildTag(child,TAG_END_ALPHA); 287 final float newEndValue = viewState.alpha; 288 if (previousEndValue != null && previousEndValue == newEndValue) { 289 return; 290 } 291 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); 292 if (!mAnimationFilter.animateAlpha) { 293 // just a local update was performed 294 if (previousAnimator != null) { 295 // we need to increase all animation keyframes of the previous animator by the 296 // relative change to the end value 297 PropertyValuesHolder[] values = previousAnimator.getValues(); 298 float relativeDiff = newEndValue - previousEndValue; 299 float newStartValue = previousStartValue + relativeDiff; 300 values[0].setFloatValues(newStartValue, newEndValue); 301 child.setTag(TAG_START_ALPHA, newStartValue); 302 child.setTag(TAG_END_ALPHA, newEndValue); 303 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 304 return; 305 } else { 306 // no new animation needed, let's just apply the value 307 child.setAlpha(newEndValue); 308 if (newEndValue == 0) { 309 child.setVisibility(View.INVISIBLE); 310 } 311 } 312 } 313 314 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA, 315 child.getAlpha(), newEndValue); 316 animator.setInterpolator(mFastOutSlowInInterpolator); 317 // Handle layer type 318 child.setLayerType(View.LAYER_TYPE_HARDWARE, null); 319 animator.addListener(new AnimatorListenerAdapter() { 320 public boolean mWasCancelled; 321 322 @Override 323 public void onAnimationEnd(Animator animation) { 324 child.setLayerType(View.LAYER_TYPE_NONE, null); 325 if (newEndValue == 0 && !mWasCancelled) { 326 child.setVisibility(View.INVISIBLE); 327 } 328 child.setTag(TAG_ANIMATOR_ALPHA, null); 329 child.setTag(TAG_START_ALPHA, null); 330 child.setTag(TAG_END_ALPHA, null); 331 } 332 333 @Override 334 public void onAnimationCancel(Animator animation) { 335 mWasCancelled = true; 336 } 337 338 @Override 339 public void onAnimationStart(Animator animation) { 340 mWasCancelled = false; 341 } 342 }); 343 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 344 animator.setDuration(newDuration); 345 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { 346 animator.setStartDelay(delay); 347 } 348 animator.addListener(getGlobalAnimationFinishedListener()); 349 // remove the tag when the animation is finished 350 animator.addListener(new AnimatorListenerAdapter() { 351 @Override 352 public void onAnimationEnd(Animator animation) { 353 354 } 355 }); 356 startAnimator(animator); 357 child.setTag(TAG_ANIMATOR_ALPHA, animator); 358 child.setTag(TAG_START_ALPHA, child.getAlpha()); 359 child.setTag(TAG_END_ALPHA, newEndValue); 360 } 361 362 private void startZTranslationAnimation(final ExpandableView child, 363 final StackScrollState.ViewState viewState, long delay) { 364 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z); 365 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); 366 float newEndValue = viewState.zTranslation; 367 if (previousEndValue != null && previousEndValue == newEndValue) { 368 return; 369 } 370 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); 371 if (!mAnimationFilter.animateZ) { 372 // just a local update was performed 373 if (previousAnimator != null) { 374 // we need to increase all animation keyframes of the previous animator by the 375 // relative change to the end value 376 PropertyValuesHolder[] values = previousAnimator.getValues(); 377 float relativeDiff = newEndValue - previousEndValue; 378 float newStartValue = previousStartValue + relativeDiff; 379 values[0].setFloatValues(newStartValue, newEndValue); 380 child.setTag(TAG_START_TRANSLATION_Z, newStartValue); 381 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 382 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 383 return; 384 } else { 385 // no new animation needed, let's just apply the value 386 child.setTranslationZ(newEndValue); 387 } 388 } 389 390 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z, 391 child.getTranslationZ(), newEndValue); 392 animator.setInterpolator(mFastOutSlowInInterpolator); 393 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 394 animator.setDuration(newDuration); 395 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { 396 animator.setStartDelay(delay); 397 } 398 animator.addListener(getGlobalAnimationFinishedListener()); 399 // remove the tag when the animation is finished 400 animator.addListener(new AnimatorListenerAdapter() { 401 @Override 402 public void onAnimationEnd(Animator animation) { 403 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null); 404 child.setTag(TAG_START_TRANSLATION_Z, null); 405 child.setTag(TAG_END_TRANSLATION_Z, null); 406 } 407 }); 408 startAnimator(animator); 409 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); 410 child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ()); 411 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 412 } 413 414 private void startYTranslationAnimation(final ExpandableView child, 415 StackScrollState.ViewState viewState, long delay) { 416 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y); 417 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); 418 float newEndValue = viewState.yTranslation; 419 if (previousEndValue != null && previousEndValue == newEndValue) { 420 return; 421 } 422 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); 423 if (!mAnimationFilter.animateY) { 424 // just a local update was performed 425 if (previousAnimator != null) { 426 // we need to increase all animation keyframes of the previous animator by the 427 // relative change to the end value 428 PropertyValuesHolder[] values = previousAnimator.getValues(); 429 float relativeDiff = newEndValue - previousEndValue; 430 float newStartValue = previousStartValue + relativeDiff; 431 values[0].setFloatValues(newStartValue, newEndValue); 432 child.setTag(TAG_START_TRANSLATION_Y, newStartValue); 433 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 434 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 435 return; 436 } else { 437 // no new animation needed, let's just apply the value 438 child.setTranslationY(newEndValue); 439 return; 440 } 441 } 442 443 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, 444 child.getTranslationY(), newEndValue); 445 animator.setInterpolator(mFastOutSlowInInterpolator); 446 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 447 animator.setDuration(newDuration); 448 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { 449 animator.setStartDelay(delay); 450 } 451 animator.addListener(getGlobalAnimationFinishedListener()); 452 // remove the tag when the animation is finished 453 animator.addListener(new AnimatorListenerAdapter() { 454 @Override 455 public void onAnimationEnd(Animator animation) { 456 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); 457 child.setTag(TAG_START_TRANSLATION_Y, null); 458 child.setTag(TAG_END_TRANSLATION_Y, null); 459 } 460 }); 461 startAnimator(animator); 462 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); 463 child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY()); 464 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 465 } 466 467 private void startScaleAnimation(final ExpandableView child, 468 StackScrollState.ViewState viewState) { 469 Float previousStartValue = getChildTag(child, TAG_START_SCALE); 470 Float previousEndValue = getChildTag(child, TAG_END_SCALE); 471 float newEndValue = viewState.scale; 472 if (previousEndValue != null && previousEndValue == newEndValue) { 473 return; 474 } 475 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE); 476 if (!mAnimationFilter.animateScale) { 477 // just a local update was performed 478 if (previousAnimator != null) { 479 // we need to increase all animation keyframes of the previous animator by the 480 // relative change to the end value 481 PropertyValuesHolder[] values = previousAnimator.getValues(); 482 float relativeDiff = newEndValue - previousEndValue; 483 float newStartValue = previousStartValue + relativeDiff; 484 values[0].setFloatValues(newStartValue, newEndValue); 485 values[1].setFloatValues(newStartValue, newEndValue); 486 child.setTag(TAG_START_SCALE, newStartValue); 487 child.setTag(TAG_END_SCALE, newEndValue); 488 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 489 return; 490 } else { 491 // no new animation needed, let's just apply the value 492 child.setScaleX(newEndValue); 493 child.setScaleY(newEndValue); 494 } 495 } 496 497 PropertyValuesHolder holderX = 498 PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue); 499 PropertyValuesHolder holderY = 500 PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue); 501 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY); 502 animator.setInterpolator(mFastOutSlowInInterpolator); 503 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 504 animator.setDuration(newDuration); 505 animator.addListener(getGlobalAnimationFinishedListener()); 506 // remove the tag when the animation is finished 507 animator.addListener(new AnimatorListenerAdapter() { 508 @Override 509 public void onAnimationEnd(Animator animation) { 510 child.setTag(TAG_ANIMATOR_SCALE, null); 511 child.setTag(TAG_START_SCALE, null); 512 child.setTag(TAG_END_SCALE, null); 513 } 514 }); 515 startAnimator(animator); 516 child.setTag(TAG_ANIMATOR_SCALE, animator); 517 child.setTag(TAG_START_SCALE, child.getScaleX()); 518 child.setTag(TAG_END_SCALE, newEndValue); 519 } 520 521 private void startAnimator(ValueAnimator animator) { 522 mAnimatorSet.add(animator); 523 animator.start(); 524 } 525 526 /** 527 * @return an adapter which ensures that onAnimationFinished is called once no animation is 528 * running anymore 529 */ 530 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() { 531 if (!mAnimationListenerPool.empty()) { 532 return mAnimationListenerPool.pop(); 533 } 534 535 // We need to create a new one, no reusable ones found 536 return new AnimatorListenerAdapter() { 537 private boolean mWasCancelled; 538 539 @Override 540 public void onAnimationEnd(Animator animation) { 541 mAnimatorSet.remove(animation); 542 if (mAnimatorSet.isEmpty() && !mWasCancelled) { 543 onAnimationFinished(); 544 } 545 mAnimationListenerPool.push(this); 546 } 547 548 @Override 549 public void onAnimationCancel(Animator animation) { 550 mWasCancelled = true; 551 } 552 553 @Override 554 public void onAnimationStart(Animator animation) { 555 mWasCancelled = false; 556 } 557 }; 558 } 559 560 private <T> T getChildTag(View child, int tag) { 561 return (T) child.getTag(tag); 562 } 563 564 /** 565 * Cancel the previous animator and get the duration of the new animation. 566 * 567 * @param previousAnimator the animator which was running before 568 * @return the new duration 569 */ 570 private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator) { 571 long newDuration = mCurrentLength; 572 if (previousAnimator != null) { 573 // We take either the desired length of the new animation or the remaining time of 574 // the previous animator, whichever is longer. 575 newDuration = Math.max(previousAnimator.getDuration() 576 - previousAnimator.getCurrentPlayTime(), newDuration); 577 previousAnimator.cancel(); 578 } 579 return newDuration; 580 } 581 582 private void onAnimationFinished() { 583 mHostLayout.onChildAnimationFinished(); 584 } 585 586 /** 587 * Process the animationEvents for a new animation 588 * 589 * @param animationEvents the animation events for the animation to perform 590 * @param finalState the final state to animate to 591 */ 592 private void processAnimationEvents( 593 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents, 594 StackScrollState finalState) { 595 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { 596 final ExpandableView changingView = (ExpandableView) event.changingView; 597 if (event.animationType == 598 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { 599 600 // This item is added, initialize it's properties. 601 StackScrollState.ViewState viewState = finalState 602 .getViewStateForView(changingView); 603 if (viewState == null) { 604 // The position for this child was never generated, let's continue. 605 continue; 606 } 607 if (changingView.getVisibility() == View.GONE) { 608 // The view was set to gone but the state never removed 609 finalState.removeViewStateForView(changingView); 610 continue; 611 } 612 changingView.setAlpha(viewState.alpha); 613 changingView.setTranslationY(viewState.yTranslation); 614 changingView.setTranslationZ(viewState.zTranslation); 615 changingView.setActualHeight(viewState.height, false); 616 mNewAddChildren.add(changingView); 617 618 } else if (event.animationType == 619 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { 620 if (changingView.getVisibility() == View.GONE) { 621 continue; 622 } 623 624 // Find the amount to translate up. This is needed in order to understand the 625 // direction of the remove animation (either downwards or upwards) 626 StackScrollState.ViewState viewState = finalState 627 .getViewStateForView(event.viewAfterChangingView); 628 int actualHeight = changingView.getActualHeight(); 629 // upwards by default 630 float translationDirection = -1.0f; 631 if (viewState != null) { 632 // there was a view after this one, Approximate the distance the next child 633 // travelled 634 translationDirection = ((viewState.yTranslation 635 - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 / 636 actualHeight); 637 translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f); 638 639 } 640 changingView.performRemoveAnimation(translationDirection, new Runnable() { 641 @Override 642 public void run() { 643 // remove the temporary overlay 644 mHostLayout.getOverlay().remove(changingView); 645 } 646 }); 647 } 648 mNewEvents.add(event); 649 } 650 } 651 652 public void animateOverScrollToAmount(float targetAmount, final boolean onTop) { 653 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); 654 cancelOverScrollAnimators(onTop); 655 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount, 656 targetAmount); 657 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD); 658 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 659 @Override 660 public void onAnimationUpdate(ValueAnimator animation) { 661 float currentOverScroll = (float) animation.getAnimatedValue(); 662 mHostLayout.setOverScrollAmount(currentOverScroll, onTop, false /* animate */, 663 false /* cancelAnimators */); 664 } 665 }); 666 overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator); 667 overScrollAnimator.start(); 668 if (onTop) { 669 mTopOverScrollAnimator = overScrollAnimator; 670 } else { 671 mBottomOverScrollAnimator = overScrollAnimator; 672 } 673 } 674 675 public void cancelOverScrollAnimators(boolean onTop) { 676 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator; 677 if (currentAnimator != null) { 678 currentAnimator.cancel(); 679 } 680 } 681} 682