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