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