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