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