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