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