StackStateAnimator.java revision 95ed59283bd25fb363d13c000a7408bcafb5e93e
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 // start hiding sensitive animation 224 child.setHideSensitive(viewState.hideSensitive, 225 mAnimationFilter.animateHideSensitive && !wasAdded, delay, duration); 226 227 // apply scrimming 228 child.setScrimAmount(viewState.scrimAmount); 229 230 if (wasAdded) { 231 child.performAddAnimation(delay, mCurrentLength); 232 } 233 if (child instanceof SpeedBumpView) { 234 finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState, 235 delay + duration); 236 } 237 } 238 239 private long calculateChildAnimationDelay(StackScrollState.ViewState viewState, 240 StackScrollState finalState) { 241 if (mAnimationFilter.hasGoToFullShadeEvent) { 242 return calculateDelayGoToFullShade(viewState); 243 } 244 long minDelay = 0; 245 for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) { 246 long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING; 247 switch (event.animationType) { 248 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: { 249 int ownIndex = viewState.notGoneIndex; 250 int changingIndex = finalState 251 .getViewStateForView(event.changingView).notGoneIndex; 252 int difference = Math.abs(ownIndex - changingIndex); 253 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE, 254 difference - 1)); 255 long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement; 256 minDelay = Math.max(delay, minDelay); 257 break; 258 } 259 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT: 260 delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL; 261 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: { 262 int ownIndex = viewState.notGoneIndex; 263 boolean noNextView = event.viewAfterChangingView == null; 264 View viewAfterChangingView = noNextView 265 ? mHostLayout.getLastChildNotGone() 266 : event.viewAfterChangingView; 267 268 int nextIndex = finalState 269 .getViewStateForView(viewAfterChangingView).notGoneIndex; 270 if (ownIndex >= nextIndex) { 271 // we only have the view afterwards 272 ownIndex++; 273 } 274 int difference = Math.abs(ownIndex - nextIndex); 275 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE, 276 difference - 1)); 277 long delay = difference * delayPerElement; 278 minDelay = Math.max(delay, minDelay); 279 break; 280 } 281 default: 282 break; 283 } 284 } 285 return minDelay; 286 } 287 288 private long calculateDelayGoToFullShade(StackScrollState.ViewState viewState) { 289 float index = viewState.notGoneIndex; 290 index = (float) Math.pow(index, 0.7f); 291 return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE); 292 } 293 294 private void startHeightAnimation(final ExpandableView child, 295 StackScrollState.ViewState viewState, long duration, long delay) { 296 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT); 297 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); 298 int newEndValue = viewState.height; 299 if (previousEndValue != null && previousEndValue == newEndValue) { 300 return; 301 } 302 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT); 303 if (!mAnimationFilter.animateHeight) { 304 // just a local update was performed 305 if (previousAnimator != null) { 306 // we need to increase all animation keyframes of the previous animator by the 307 // relative change to the end value 308 PropertyValuesHolder[] values = previousAnimator.getValues(); 309 int relativeDiff = newEndValue - previousEndValue; 310 int newStartValue = previousStartValue + relativeDiff; 311 values[0].setIntValues(newStartValue, newEndValue); 312 child.setTag(TAG_START_HEIGHT, newStartValue); 313 child.setTag(TAG_END_HEIGHT, newEndValue); 314 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 315 return; 316 } else { 317 // no new animation needed, let's just apply the value 318 child.setActualHeight(newEndValue, false); 319 return; 320 } 321 } 322 323 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue); 324 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 325 @Override 326 public void onAnimationUpdate(ValueAnimator animation) { 327 child.setActualHeight((int) animation.getAnimatedValue(), 328 false /* notifyListeners */); 329 } 330 }); 331 animator.setInterpolator(mFastOutSlowInInterpolator); 332 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); 333 animator.setDuration(newDuration); 334 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { 335 animator.setStartDelay(delay); 336 } 337 animator.addListener(getGlobalAnimationFinishedListener()); 338 // remove the tag when the animation is finished 339 animator.addListener(new AnimatorListenerAdapter() { 340 @Override 341 public void onAnimationEnd(Animator animation) { 342 child.setTag(TAG_ANIMATOR_HEIGHT, null); 343 child.setTag(TAG_START_HEIGHT, null); 344 child.setTag(TAG_END_HEIGHT, null); 345 } 346 }); 347 startAnimator(animator); 348 child.setTag(TAG_ANIMATOR_HEIGHT, animator); 349 child.setTag(TAG_START_HEIGHT, child.getActualHeight()); 350 child.setTag(TAG_END_HEIGHT, newEndValue); 351 } 352 353 private void startInsetAnimation(final ExpandableView child, 354 StackScrollState.ViewState viewState, long duration, long delay) { 355 Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET); 356 Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET); 357 int newEndValue = viewState.clipTopAmount; 358 if (previousEndValue != null && previousEndValue == newEndValue) { 359 return; 360 } 361 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET); 362 if (!mAnimationFilter.animateTopInset) { 363 // just a local update was performed 364 if (previousAnimator != null) { 365 // we need to increase all animation keyframes of the previous animator by the 366 // relative change to the end value 367 PropertyValuesHolder[] values = previousAnimator.getValues(); 368 int relativeDiff = newEndValue - previousEndValue; 369 int newStartValue = previousStartValue + relativeDiff; 370 values[0].setIntValues(newStartValue, newEndValue); 371 child.setTag(TAG_START_TOP_INSET, newStartValue); 372 child.setTag(TAG_END_TOP_INSET, newEndValue); 373 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 374 return; 375 } else { 376 // no new animation needed, let's just apply the value 377 child.setClipTopAmount(newEndValue); 378 return; 379 } 380 } 381 382 ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue); 383 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 384 @Override 385 public void onAnimationUpdate(ValueAnimator animation) { 386 child.setClipTopAmount((int) animation.getAnimatedValue()); 387 } 388 }); 389 animator.setInterpolator(mFastOutSlowInInterpolator); 390 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); 391 animator.setDuration(newDuration); 392 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { 393 animator.setStartDelay(delay); 394 } 395 animator.addListener(getGlobalAnimationFinishedListener()); 396 // remove the tag when the animation is finished 397 animator.addListener(new AnimatorListenerAdapter() { 398 @Override 399 public void onAnimationEnd(Animator animation) { 400 child.setTag(TAG_ANIMATOR_TOP_INSET, null); 401 child.setTag(TAG_START_TOP_INSET, null); 402 child.setTag(TAG_END_TOP_INSET, null); 403 } 404 }); 405 startAnimator(animator); 406 child.setTag(TAG_ANIMATOR_TOP_INSET, animator); 407 child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount()); 408 child.setTag(TAG_END_TOP_INSET, newEndValue); 409 } 410 411 private void startAlphaAnimation(final ExpandableView child, 412 final StackScrollState.ViewState viewState, long duration, long delay) { 413 Float previousStartValue = getChildTag(child,TAG_START_ALPHA); 414 Float previousEndValue = getChildTag(child,TAG_END_ALPHA); 415 final float newEndValue = viewState.alpha; 416 if (previousEndValue != null && previousEndValue == newEndValue) { 417 return; 418 } 419 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); 420 if (!mAnimationFilter.animateAlpha) { 421 // just a local update was performed 422 if (previousAnimator != null) { 423 // we need to increase all animation keyframes of the previous animator by the 424 // relative change to the end value 425 PropertyValuesHolder[] values = previousAnimator.getValues(); 426 float relativeDiff = newEndValue - previousEndValue; 427 float newStartValue = previousStartValue + relativeDiff; 428 values[0].setFloatValues(newStartValue, newEndValue); 429 child.setTag(TAG_START_ALPHA, newStartValue); 430 child.setTag(TAG_END_ALPHA, newEndValue); 431 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 432 return; 433 } else { 434 // no new animation needed, let's just apply the value 435 child.setAlpha(newEndValue); 436 if (newEndValue == 0) { 437 child.setVisibility(View.INVISIBLE); 438 } 439 } 440 } 441 442 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA, 443 child.getAlpha(), newEndValue); 444 animator.setInterpolator(mFastOutSlowInInterpolator); 445 // Handle layer type 446 child.setLayerType(View.LAYER_TYPE_HARDWARE, null); 447 animator.addListener(new AnimatorListenerAdapter() { 448 public boolean mWasCancelled; 449 450 @Override 451 public void onAnimationEnd(Animator animation) { 452 child.setLayerType(View.LAYER_TYPE_NONE, null); 453 if (newEndValue == 0 && !mWasCancelled) { 454 child.setVisibility(View.INVISIBLE); 455 } 456 child.setTag(TAG_ANIMATOR_ALPHA, null); 457 child.setTag(TAG_START_ALPHA, null); 458 child.setTag(TAG_END_ALPHA, null); 459 } 460 461 @Override 462 public void onAnimationCancel(Animator animation) { 463 mWasCancelled = true; 464 } 465 466 @Override 467 public void onAnimationStart(Animator animation) { 468 mWasCancelled = false; 469 } 470 }); 471 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); 472 animator.setDuration(newDuration); 473 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { 474 animator.setStartDelay(delay); 475 } 476 animator.addListener(getGlobalAnimationFinishedListener()); 477 // remove the tag when the animation is finished 478 animator.addListener(new AnimatorListenerAdapter() { 479 @Override 480 public void onAnimationEnd(Animator animation) { 481 482 } 483 }); 484 startAnimator(animator); 485 child.setTag(TAG_ANIMATOR_ALPHA, animator); 486 child.setTag(TAG_START_ALPHA, child.getAlpha()); 487 child.setTag(TAG_END_ALPHA, newEndValue); 488 } 489 490 private void startZTranslationAnimation(final ExpandableView child, 491 final StackScrollState.ViewState viewState, long duration, long delay) { 492 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z); 493 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); 494 float newEndValue = viewState.zTranslation; 495 if (previousEndValue != null && previousEndValue == newEndValue) { 496 return; 497 } 498 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); 499 if (!mAnimationFilter.animateZ) { 500 // just a local update was performed 501 if (previousAnimator != null) { 502 // we need to increase all animation keyframes of the previous animator by the 503 // relative change to the end value 504 PropertyValuesHolder[] values = previousAnimator.getValues(); 505 float relativeDiff = newEndValue - previousEndValue; 506 float newStartValue = previousStartValue + relativeDiff; 507 values[0].setFloatValues(newStartValue, newEndValue); 508 child.setTag(TAG_START_TRANSLATION_Z, newStartValue); 509 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 510 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 511 return; 512 } else { 513 // no new animation needed, let's just apply the value 514 child.setTranslationZ(newEndValue); 515 } 516 } 517 518 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z, 519 child.getTranslationZ(), newEndValue); 520 animator.setInterpolator(mFastOutSlowInInterpolator); 521 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); 522 animator.setDuration(newDuration); 523 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { 524 animator.setStartDelay(delay); 525 } 526 animator.addListener(getGlobalAnimationFinishedListener()); 527 // remove the tag when the animation is finished 528 animator.addListener(new AnimatorListenerAdapter() { 529 @Override 530 public void onAnimationEnd(Animator animation) { 531 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null); 532 child.setTag(TAG_START_TRANSLATION_Z, null); 533 child.setTag(TAG_END_TRANSLATION_Z, null); 534 } 535 }); 536 startAnimator(animator); 537 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); 538 child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ()); 539 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 540 } 541 542 private void startYTranslationAnimation(final ExpandableView child, 543 StackScrollState.ViewState viewState, long duration, long delay) { 544 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y); 545 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); 546 float newEndValue = viewState.yTranslation; 547 if (previousEndValue != null && previousEndValue == newEndValue) { 548 return; 549 } 550 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); 551 if (!mAnimationFilter.animateY) { 552 // just a local update was performed 553 if (previousAnimator != null) { 554 // we need to increase all animation keyframes of the previous animator by the 555 // relative change to the end value 556 PropertyValuesHolder[] values = previousAnimator.getValues(); 557 float relativeDiff = newEndValue - previousEndValue; 558 float newStartValue = previousStartValue + relativeDiff; 559 values[0].setFloatValues(newStartValue, newEndValue); 560 child.setTag(TAG_START_TRANSLATION_Y, newStartValue); 561 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 562 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 563 return; 564 } else { 565 // no new animation needed, let's just apply the value 566 child.setTranslationY(newEndValue); 567 return; 568 } 569 } 570 571 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, 572 child.getTranslationY(), newEndValue); 573 animator.setInterpolator(mFastOutSlowInInterpolator); 574 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); 575 animator.setDuration(newDuration); 576 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { 577 animator.setStartDelay(delay); 578 } 579 animator.addListener(getGlobalAnimationFinishedListener()); 580 // remove the tag when the animation is finished 581 animator.addListener(new AnimatorListenerAdapter() { 582 @Override 583 public void onAnimationEnd(Animator animation) { 584 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); 585 child.setTag(TAG_START_TRANSLATION_Y, null); 586 child.setTag(TAG_END_TRANSLATION_Y, null); 587 } 588 }); 589 startAnimator(animator); 590 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); 591 child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY()); 592 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 593 } 594 595 private void startScaleAnimation(final ExpandableView child, 596 StackScrollState.ViewState viewState, long duration) { 597 Float previousStartValue = getChildTag(child, TAG_START_SCALE); 598 Float previousEndValue = getChildTag(child, TAG_END_SCALE); 599 float newEndValue = viewState.scale; 600 if (previousEndValue != null && previousEndValue == newEndValue) { 601 return; 602 } 603 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE); 604 if (!mAnimationFilter.animateScale) { 605 // just a local update was performed 606 if (previousAnimator != null) { 607 // we need to increase all animation keyframes of the previous animator by the 608 // relative change to the end value 609 PropertyValuesHolder[] values = previousAnimator.getValues(); 610 float relativeDiff = newEndValue - previousEndValue; 611 float newStartValue = previousStartValue + relativeDiff; 612 values[0].setFloatValues(newStartValue, newEndValue); 613 values[1].setFloatValues(newStartValue, newEndValue); 614 child.setTag(TAG_START_SCALE, newStartValue); 615 child.setTag(TAG_END_SCALE, newEndValue); 616 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 617 return; 618 } else { 619 // no new animation needed, let's just apply the value 620 child.setScaleX(newEndValue); 621 child.setScaleY(newEndValue); 622 } 623 } 624 625 PropertyValuesHolder holderX = 626 PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue); 627 PropertyValuesHolder holderY = 628 PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue); 629 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY); 630 animator.setInterpolator(mFastOutSlowInInterpolator); 631 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); 632 animator.setDuration(newDuration); 633 animator.addListener(getGlobalAnimationFinishedListener()); 634 // remove the tag when the animation is finished 635 animator.addListener(new AnimatorListenerAdapter() { 636 @Override 637 public void onAnimationEnd(Animator animation) { 638 child.setTag(TAG_ANIMATOR_SCALE, null); 639 child.setTag(TAG_START_SCALE, null); 640 child.setTag(TAG_END_SCALE, null); 641 } 642 }); 643 startAnimator(animator); 644 child.setTag(TAG_ANIMATOR_SCALE, animator); 645 child.setTag(TAG_START_SCALE, child.getScaleX()); 646 child.setTag(TAG_END_SCALE, newEndValue); 647 } 648 649 private void startAnimator(ValueAnimator animator) { 650 mAnimatorSet.add(animator); 651 animator.start(); 652 } 653 654 /** 655 * @return an adapter which ensures that onAnimationFinished is called once no animation is 656 * running anymore 657 */ 658 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() { 659 if (!mAnimationListenerPool.empty()) { 660 return mAnimationListenerPool.pop(); 661 } 662 663 // We need to create a new one, no reusable ones found 664 return new AnimatorListenerAdapter() { 665 private boolean mWasCancelled; 666 667 @Override 668 public void onAnimationEnd(Animator animation) { 669 mAnimatorSet.remove(animation); 670 if (mAnimatorSet.isEmpty() && !mWasCancelled) { 671 onAnimationFinished(); 672 } 673 mAnimationListenerPool.push(this); 674 } 675 676 @Override 677 public void onAnimationCancel(Animator animation) { 678 mWasCancelled = true; 679 } 680 681 @Override 682 public void onAnimationStart(Animator animation) { 683 mWasCancelled = false; 684 } 685 }; 686 } 687 688 private <T> T getChildTag(View child, int tag) { 689 return (T) child.getTag(tag); 690 } 691 692 /** 693 * Cancel the previous animator and get the duration of the new animation. 694 * 695 * @param duration the new duration 696 * @param previousAnimator the animator which was running before 697 * @return the new duration 698 */ 699 private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) { 700 long newDuration = duration; 701 if (previousAnimator != null) { 702 // We take either the desired length of the new animation or the remaining time of 703 // the previous animator, whichever is longer. 704 newDuration = Math.max(previousAnimator.getDuration() 705 - previousAnimator.getCurrentPlayTime(), newDuration); 706 previousAnimator.cancel(); 707 } 708 return newDuration; 709 } 710 711 private void onAnimationFinished() { 712 mHostLayout.onChildAnimationFinished(); 713 } 714 715 /** 716 * Process the animationEvents for a new animation 717 * 718 * @param animationEvents the animation events for the animation to perform 719 * @param finalState the final state to animate to 720 */ 721 private void processAnimationEvents( 722 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents, 723 StackScrollState finalState) { 724 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { 725 final ExpandableView changingView = (ExpandableView) event.changingView; 726 if (event.animationType == 727 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { 728 729 // This item is added, initialize it's properties. 730 StackScrollState.ViewState viewState = finalState 731 .getViewStateForView(changingView); 732 if (viewState == null) { 733 // The position for this child was never generated, let's continue. 734 continue; 735 } 736 if (changingView.getVisibility() == View.GONE) { 737 // The view was set to gone but the state never removed 738 finalState.removeViewStateForView(changingView); 739 continue; 740 } 741 changingView.setAlpha(viewState.alpha); 742 changingView.setTranslationY(viewState.yTranslation); 743 changingView.setTranslationZ(viewState.zTranslation); 744 changingView.setActualHeight(viewState.height, false); 745 mNewAddChildren.add(changingView); 746 747 } else if (event.animationType == 748 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { 749 if (changingView.getVisibility() == View.GONE) { 750 mHostLayout.getOverlay().remove(changingView); 751 continue; 752 } 753 754 // Find the amount to translate up. This is needed in order to understand the 755 // direction of the remove animation (either downwards or upwards) 756 StackScrollState.ViewState viewState = finalState 757 .getViewStateForView(event.viewAfterChangingView); 758 int actualHeight = changingView.getActualHeight(); 759 // upwards by default 760 float translationDirection = -1.0f; 761 if (viewState != null) { 762 // there was a view after this one, Approximate the distance the next child 763 // travelled 764 translationDirection = ((viewState.yTranslation 765 - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 / 766 actualHeight); 767 translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f); 768 769 } 770 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 771 translationDirection, new Runnable() { 772 @Override 773 public void run() { 774 // remove the temporary overlay 775 mHostLayout.getOverlay().remove(changingView); 776 } 777 }); 778 } 779 mNewEvents.add(event); 780 } 781 } 782 783 public void animateOverScrollToAmount(float targetAmount, final boolean onTop, 784 final boolean isRubberbanded) { 785 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); 786 if (targetAmount == startOverScrollAmount) { 787 return; 788 } 789 cancelOverScrollAnimators(onTop); 790 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount, 791 targetAmount); 792 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD); 793 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 794 @Override 795 public void onAnimationUpdate(ValueAnimator animation) { 796 float currentOverScroll = (float) animation.getAnimatedValue(); 797 mHostLayout.setOverScrollAmount( 798 currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */, 799 isRubberbanded); 800 } 801 }); 802 overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator); 803 overScrollAnimator.addListener(new AnimatorListenerAdapter() { 804 @Override 805 public void onAnimationEnd(Animator animation) { 806 if (onTop) { 807 mTopOverScrollAnimator = null; 808 } else { 809 mBottomOverScrollAnimator = null; 810 } 811 } 812 }); 813 overScrollAnimator.start(); 814 if (onTop) { 815 mTopOverScrollAnimator = overScrollAnimator; 816 } else { 817 mBottomOverScrollAnimator = overScrollAnimator; 818 } 819 } 820 821 public void cancelOverScrollAnimators(boolean onTop) { 822 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator; 823 if (currentAnimator != null) { 824 currentAnimator.cancel(); 825 } 826 } 827} 828