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