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