1 /* 2 * Copyright (C) 2015 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 17 package com.android.systemui.statusbar.notification.stack; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.PropertyValuesHolder; 23 import android.animation.ValueAnimator; 24 import android.util.Log; 25 import android.util.Property; 26 import android.view.View; 27 import android.view.animation.Interpolator; 28 29 import com.android.app.animation.Interpolators; 30 import com.android.systemui.Dumpable; 31 import com.android.systemui.res.R; 32 import com.android.systemui.statusbar.notification.AnimatableProperty; 33 import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification; 34 import com.android.systemui.statusbar.notification.PropertyAnimator; 35 import com.android.systemui.statusbar.notification.row.ExpandableView; 36 import com.android.systemui.statusbar.policy.HeadsUpUtil; 37 38 import java.io.PrintWriter; 39 import java.lang.reflect.Field; 40 import java.lang.reflect.Modifier; 41 42 /** 43 * A state of a view. This can be used to apply a set of view properties to a view with 44 * {@link com.android.systemui.statusbar.notification.stack.StackScrollState} or start 45 * animations with {@link com.android.systemui.statusbar.notification.stack.StackStateAnimator}. 46 */ 47 public class ViewState implements Dumpable { 48 49 /** 50 * Some animation properties that can be used to update running animations but not creating 51 * any new ones. 52 */ 53 protected static final AnimationProperties NO_NEW_ANIMATIONS = new AnimationProperties() { 54 AnimationFilter mAnimationFilter = new AnimationFilter(); 55 56 @Override 57 public AnimationFilter getAnimationFilter() { 58 return mAnimationFilter; 59 } 60 }; 61 private static final int TAG_ANIMATOR_TRANSLATION_X = R.id.translation_x_animator_tag; 62 private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; 63 private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; 64 private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag; 65 private static final int TAG_END_TRANSLATION_X = R.id.translation_x_animator_end_value_tag; 66 private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag; 67 private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag; 68 private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag; 69 private static final int TAG_START_TRANSLATION_X = R.id.translation_x_animator_start_value_tag; 70 private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag; 71 private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag; 72 private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag; 73 private static final String LOG_TAG = "StackViewState"; 74 75 private static final AnimatableProperty SCALE_X_PROPERTY 76 = new AnimatableProperty() { 77 78 @Override 79 public int getAnimationStartTag() { 80 return R.id.scale_x_animator_start_value_tag; 81 } 82 83 @Override 84 public int getAnimationEndTag() { 85 return R.id.scale_x_animator_end_value_tag; 86 } 87 88 @Override 89 public int getAnimatorTag() { 90 return R.id.scale_x_animator_tag; 91 } 92 93 @Override 94 public Property getProperty() { 95 return View.SCALE_X; 96 } 97 }; 98 99 private static final AnimatableProperty SCALE_Y_PROPERTY 100 = new AnimatableProperty() { 101 102 @Override 103 public int getAnimationStartTag() { 104 return R.id.scale_y_animator_start_value_tag; 105 } 106 107 @Override 108 public int getAnimationEndTag() { 109 return R.id.scale_y_animator_end_value_tag; 110 } 111 112 @Override 113 public int getAnimatorTag() { 114 return R.id.scale_y_animator_tag; 115 } 116 117 @Override 118 public Property getProperty() { 119 return View.SCALE_Y; 120 } 121 }; 122 123 public boolean gone; 124 public boolean hidden; 125 126 private float mAlpha; 127 private float mXTranslation; 128 private float mYTranslation; 129 private float mZTranslation; 130 private float mScaleX = 1.0f; 131 private float mScaleY = 1.0f; 132 getAlpha()133 public float getAlpha() { 134 return mAlpha; 135 } 136 137 /** 138 * @param alpha View transparency. 139 */ setAlpha(float alpha)140 public void setAlpha(float alpha) { 141 if (isValidFloat(alpha, "alpha")) { 142 this.mAlpha = alpha; 143 } 144 } 145 getXTranslation()146 public float getXTranslation() { 147 return mXTranslation; 148 } 149 150 /** 151 * @param xTranslation x-axis translation value for the animation. 152 */ setXTranslation(float xTranslation)153 public void setXTranslation(float xTranslation) { 154 if (isValidFloat(xTranslation, "xTranslation")) { 155 this.mXTranslation = xTranslation; 156 } 157 } 158 getYTranslation()159 public float getYTranslation() { 160 return mYTranslation; 161 } 162 163 /** 164 * @param yTranslation y-axis translation value for the animation. 165 */ setYTranslation(float yTranslation)166 public void setYTranslation(float yTranslation) { 167 if (isValidFloat(yTranslation, "yTranslation")) { 168 this.mYTranslation = yTranslation; 169 } 170 } 171 getZTranslation()172 public float getZTranslation() { 173 return mZTranslation; 174 } 175 176 177 /** 178 * @param zTranslation z-axis translation value for the animation. 179 */ setZTranslation(float zTranslation)180 public void setZTranslation(float zTranslation) { 181 if (isValidFloat(zTranslation, "zTranslation")) { 182 this.mZTranslation = zTranslation; 183 } 184 } 185 getScaleX()186 public float getScaleX() { 187 return mScaleX; 188 } 189 190 /** 191 * @param scaleX x-axis scale property for the animation. 192 */ setScaleX(float scaleX)193 public void setScaleX(float scaleX) { 194 if (isValidFloat(scaleX, "scaleX")) { 195 this.mScaleX = scaleX; 196 } 197 } 198 getScaleY()199 public float getScaleY() { 200 return mScaleY; 201 } 202 203 /** 204 * @param scaleY y-axis scale property for the animation. 205 */ setScaleY(float scaleY)206 public void setScaleY(float scaleY) { 207 if (isValidFloat(scaleY, "scaleY")) { 208 this.mScaleY = scaleY; 209 } 210 } 211 212 /** 213 * Checks if {@code value} is a valid float value. If it is not, logs it (using {@code name}) 214 * and returns false. 215 */ isValidFloat(float value, String name)216 private boolean isValidFloat(float value, String name) { 217 if (Float.isNaN(value)) { 218 Log.wtf(LOG_TAG, "Cannot set property " + name + " to NaN"); 219 return false; 220 } 221 return true; 222 } 223 copyFrom(ViewState viewState)224 public void copyFrom(ViewState viewState) { 225 mAlpha = viewState.mAlpha; 226 mXTranslation = viewState.mXTranslation; 227 mYTranslation = viewState.mYTranslation; 228 mZTranslation = viewState.mZTranslation; 229 gone = viewState.gone; 230 hidden = viewState.hidden; 231 mScaleX = viewState.mScaleX; 232 mScaleY = viewState.mScaleY; 233 } 234 initFrom(View view)235 public void initFrom(View view) { 236 mAlpha = view.getAlpha(); 237 mXTranslation = view.getTranslationX(); 238 mYTranslation = view.getTranslationY(); 239 mZTranslation = view.getTranslationZ(); 240 gone = view.getVisibility() == View.GONE; 241 hidden = view.getVisibility() == View.INVISIBLE; 242 mScaleX = view.getScaleX(); 243 mScaleY = view.getScaleY(); 244 } 245 246 /** 247 * Applies a {@link ViewState} to a normal view. 248 */ applyToView(View view)249 public void applyToView(View view) { 250 if (this.gone) { 251 // don't do anything with it 252 return; 253 } 254 255 // apply xTranslation 256 boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X); 257 if (animatingX) { 258 updateAnimationX(view); 259 } else if (view.getTranslationX() != this.mXTranslation) { 260 view.setTranslationX(this.mXTranslation); 261 } 262 263 // apply yTranslation 264 boolean animatingY = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y); 265 if (animatingY) { 266 updateAnimationY(view); 267 } else if (view.getTranslationY() != this.mYTranslation) { 268 view.setTranslationY(this.mYTranslation); 269 } 270 271 // apply zTranslation 272 boolean animatingZ = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z); 273 if (animatingZ) { 274 updateAnimationZ(view); 275 } else if (view.getTranslationZ() != this.mZTranslation) { 276 view.setTranslationZ(this.mZTranslation); 277 } 278 279 // apply scaleX 280 boolean animatingScaleX = isAnimating(view, SCALE_X_PROPERTY); 281 if (animatingScaleX) { 282 updateAnimation(view, SCALE_X_PROPERTY, mScaleX); 283 } else if (view.getScaleX() != mScaleX) { 284 view.setScaleX(mScaleX); 285 } 286 287 // apply scaleY 288 boolean animatingScaleY = isAnimating(view, SCALE_Y_PROPERTY); 289 if (animatingScaleY) { 290 updateAnimation(view, SCALE_Y_PROPERTY, mScaleY); 291 } else if (view.getScaleY() != mScaleY) { 292 view.setScaleY(mScaleY); 293 } 294 295 int oldVisibility = view.getVisibility(); 296 boolean becomesInvisible = this.mAlpha == 0.0f 297 || (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE)); 298 boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA); 299 if (animatingAlpha) { 300 updateAlphaAnimation(view); 301 } else if (view.getAlpha() != this.mAlpha) { 302 // apply layer type 303 boolean becomesFullyVisible = this.mAlpha == 1.0f; 304 boolean becomesFaded = !becomesInvisible && !becomesFullyVisible; 305 if (FadeOptimizedNotification.FADE_LAYER_OPTIMIZATION_ENABLED 306 && view instanceof FadeOptimizedNotification) { 307 // NOTE: A view that's going to utilize this interface to avoid having a hardware 308 // layer will have to return false from hasOverlappingRendering(), so we 309 // intentionally do not check that value in this if, even though we do in the else. 310 FadeOptimizedNotification fadeOptimizedView = (FadeOptimizedNotification) view; 311 boolean isFaded = fadeOptimizedView.isNotificationFaded(); 312 if (isFaded != becomesFaded) { 313 fadeOptimizedView.setNotificationFaded(becomesFaded); 314 } 315 } else { 316 boolean newLayerTypeIsHardware = becomesFaded && view.hasOverlappingRendering(); 317 int layerType = view.getLayerType(); 318 int newLayerType = newLayerTypeIsHardware 319 ? View.LAYER_TYPE_HARDWARE 320 : View.LAYER_TYPE_NONE; 321 if (layerType != newLayerType) { 322 view.setLayerType(newLayerType, null); 323 } 324 } 325 326 // apply alpha 327 view.setAlpha(this.mAlpha); 328 } 329 330 // apply visibility 331 int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; 332 if (newVisibility != oldVisibility) { 333 if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) { 334 // We don't want views to change visibility when they are animating to GONE 335 view.setVisibility(newVisibility); 336 } 337 } 338 } 339 isAnimating(View view)340 public boolean isAnimating(View view) { 341 if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_X)) { 342 return true; 343 } 344 if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y)) { 345 return true; 346 } 347 if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z)) { 348 return true; 349 } 350 if (isAnimating(view, TAG_ANIMATOR_ALPHA)) { 351 return true; 352 } 353 if (isAnimating(view, SCALE_X_PROPERTY)) { 354 return true; 355 } 356 if (isAnimating(view, SCALE_Y_PROPERTY)) { 357 return true; 358 } 359 return false; 360 } 361 isAnimating(View view, int tag)362 private static boolean isAnimating(View view, int tag) { 363 return getChildTag(view, tag) != null; 364 } 365 isAnimating(View view, AnimatableProperty property)366 public static boolean isAnimating(View view, AnimatableProperty property) { 367 return getChildTag(view, property.getAnimatorTag()) != null; 368 } 369 370 /** 371 * Start an animation to this viewstate 372 * 373 * @param child the view to animate 374 * @param animationProperties the properties of the animation 375 */ animateTo(View child, AnimationProperties animationProperties)376 public void animateTo(View child, AnimationProperties animationProperties) { 377 boolean wasVisible = child.getVisibility() == View.VISIBLE; 378 final float alpha = this.mAlpha; 379 if (!wasVisible && (alpha != 0 || child.getAlpha() != 0) 380 && !this.gone && !this.hidden) { 381 child.setVisibility(View.VISIBLE); 382 } 383 float childAlpha = child.getAlpha(); 384 boolean alphaChanging = this.mAlpha != childAlpha; 385 if (child instanceof ExpandableView) { 386 // We don't want views to change visibility when they are animating to GONE 387 alphaChanging &= !((ExpandableView) child).willBeGone(); 388 } 389 390 // start translationX animation 391 if (child.getTranslationX() != this.mXTranslation) { 392 startXTranslationAnimation(child, animationProperties); 393 } else { 394 abortAnimation(child, TAG_ANIMATOR_TRANSLATION_X); 395 } 396 397 // start translationY animation 398 if (child.getTranslationY() != this.mYTranslation) { 399 startYTranslationAnimation(child, animationProperties); 400 } else { 401 abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y); 402 } 403 404 // start translationZ animation 405 if (child.getTranslationZ() != this.mZTranslation) { 406 startZTranslationAnimation(child, animationProperties); 407 } else { 408 abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z); 409 } 410 411 // start scaleX animation 412 if (child.getScaleX() != mScaleX) { 413 PropertyAnimator.startAnimation(child, SCALE_X_PROPERTY, mScaleX, animationProperties); 414 } else { 415 abortAnimation(child, SCALE_X_PROPERTY.getAnimatorTag()); 416 } 417 418 // start scaleX animation 419 if (child.getScaleY() != mScaleY) { 420 PropertyAnimator.startAnimation(child, SCALE_Y_PROPERTY, mScaleY, animationProperties); 421 } else { 422 abortAnimation(child, SCALE_Y_PROPERTY.getAnimatorTag()); 423 } 424 425 // start alpha animation 426 if (alphaChanging) { 427 startAlphaAnimation(child, animationProperties); 428 } else { 429 abortAnimation(child, TAG_ANIMATOR_ALPHA); 430 } 431 } 432 updateAlphaAnimation(View view)433 private void updateAlphaAnimation(View view) { 434 startAlphaAnimation(view, NO_NEW_ANIMATIONS); 435 } 436 startAlphaAnimation(final View child, AnimationProperties properties)437 private void startAlphaAnimation(final View child, AnimationProperties properties) { 438 Float previousStartValue = getChildTag(child, TAG_START_ALPHA); 439 Float previousEndValue = getChildTag(child, TAG_END_ALPHA); 440 final float newEndValue = this.mAlpha; 441 if (previousEndValue != null && previousEndValue == newEndValue) { 442 return; 443 } 444 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); 445 AnimationFilter filter = properties.getAnimationFilter(); 446 if (!filter.animateAlpha) { 447 // just a local update was performed 448 if (previousAnimator != null) { 449 // we need to increase all animation keyframes of the previous animator by the 450 // relative change to the end value 451 PropertyValuesHolder[] values = previousAnimator.getValues(); 452 float relativeDiff = newEndValue - previousEndValue; 453 float newStartValue = previousStartValue + relativeDiff; 454 values[0].setFloatValues(newStartValue, newEndValue); 455 child.setTag(TAG_START_ALPHA, newStartValue); 456 child.setTag(TAG_END_ALPHA, newEndValue); 457 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 458 return; 459 } else { 460 // no new animation needed, let's just apply the value 461 child.setAlpha(newEndValue); 462 if (newEndValue == 0) { 463 child.setVisibility(View.INVISIBLE); 464 } 465 } 466 } 467 468 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA, 469 child.getAlpha(), newEndValue); 470 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 471 // Handle layer type 472 child.setLayerType(View.LAYER_TYPE_HARDWARE, null); 473 animator.addListener(new AnimatorListenerAdapter() { 474 public boolean mWasCancelled; 475 476 @Override 477 public void onAnimationEnd(Animator animation) { 478 child.setLayerType(View.LAYER_TYPE_NONE, null); 479 if (newEndValue == 0 && !mWasCancelled) { 480 child.setVisibility(View.INVISIBLE); 481 } 482 // remove the tag when the animation is finished 483 child.setTag(TAG_ANIMATOR_ALPHA, null); 484 child.setTag(TAG_START_ALPHA, null); 485 child.setTag(TAG_END_ALPHA, null); 486 } 487 488 @Override 489 public void onAnimationCancel(Animator animation) { 490 mWasCancelled = true; 491 } 492 493 @Override 494 public void onAnimationStart(Animator animation) { 495 mWasCancelled = false; 496 } 497 }); 498 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); 499 animator.setDuration(newDuration); 500 if (properties.delay > 0 && (previousAnimator == null 501 || previousAnimator.getAnimatedFraction() == 0)) { 502 animator.setStartDelay(properties.delay); 503 } 504 AnimatorListenerAdapter listener = properties.getAnimationFinishListener(View.ALPHA); 505 if (listener != null) { 506 animator.addListener(listener); 507 } 508 509 startAnimator(animator, listener); 510 child.setTag(TAG_ANIMATOR_ALPHA, animator); 511 child.setTag(TAG_START_ALPHA, child.getAlpha()); 512 child.setTag(TAG_END_ALPHA, newEndValue); 513 } 514 updateAnimationZ(View view)515 private void updateAnimationZ(View view) { 516 startZTranslationAnimation(view, NO_NEW_ANIMATIONS); 517 } 518 updateAnimation(View view, AnimatableProperty property, float endValue)519 private void updateAnimation(View view, AnimatableProperty property, 520 float endValue) { 521 PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS); 522 } 523 startZTranslationAnimation(final View child, AnimationProperties properties)524 private void startZTranslationAnimation(final View child, AnimationProperties properties) { 525 Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_Z); 526 Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_Z); 527 float newEndValue = this.mZTranslation; 528 if (previousEndValue != null && previousEndValue == newEndValue) { 529 return; 530 } 531 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); 532 AnimationFilter filter = properties.getAnimationFilter(); 533 if (!filter.animateZ) { 534 // just a local update was performed 535 if (previousAnimator != null) { 536 // we need to increase all animation keyframes of the previous animator by the 537 // relative change to the end value 538 PropertyValuesHolder[] values = previousAnimator.getValues(); 539 float relativeDiff = newEndValue - previousEndValue; 540 float newStartValue = previousStartValue + relativeDiff; 541 values[0].setFloatValues(newStartValue, newEndValue); 542 child.setTag(TAG_START_TRANSLATION_Z, newStartValue); 543 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 544 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 545 return; 546 } else { 547 // no new animation needed, let's just apply the value 548 child.setTranslationZ(newEndValue); 549 } 550 } 551 552 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z, 553 child.getTranslationZ(), newEndValue); 554 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 555 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); 556 animator.setDuration(newDuration); 557 if (properties.delay > 0 && (previousAnimator == null 558 || previousAnimator.getAnimatedFraction() == 0)) { 559 animator.setStartDelay(properties.delay); 560 } 561 AnimatorListenerAdapter listener = properties.getAnimationFinishListener( 562 View.TRANSLATION_Z); 563 if (listener != null) { 564 animator.addListener(listener); 565 } 566 // remove the tag when the animation is finished 567 animator.addListener(new AnimatorListenerAdapter() { 568 @Override 569 public void onAnimationEnd(Animator animation) { 570 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null); 571 child.setTag(TAG_START_TRANSLATION_Z, null); 572 child.setTag(TAG_END_TRANSLATION_Z, null); 573 } 574 }); 575 startAnimator(animator, listener); 576 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); 577 child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ()); 578 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 579 } 580 updateAnimationX(View view)581 private void updateAnimationX(View view) { 582 startXTranslationAnimation(view, NO_NEW_ANIMATIONS); 583 } 584 startXTranslationAnimation(final View child, AnimationProperties properties)585 private void startXTranslationAnimation(final View child, AnimationProperties properties) { 586 Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_X); 587 Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_X); 588 float newEndValue = this.mXTranslation; 589 if (previousEndValue != null && previousEndValue == newEndValue) { 590 return; 591 } 592 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_X); 593 AnimationFilter filter = properties.getAnimationFilter(); 594 if (!filter.animateX) { 595 // just a local update was performed 596 if (previousAnimator != null) { 597 // we need to increase all animation keyframes of the previous animator by the 598 // relative change to the end value 599 PropertyValuesHolder[] values = previousAnimator.getValues(); 600 float relativeDiff = newEndValue - previousEndValue; 601 float newStartValue = previousStartValue + relativeDiff; 602 values[0].setFloatValues(newStartValue, newEndValue); 603 child.setTag(TAG_START_TRANSLATION_X, newStartValue); 604 child.setTag(TAG_END_TRANSLATION_X, newEndValue); 605 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 606 return; 607 } else { 608 // no new animation needed, let's just apply the value 609 child.setTranslationX(newEndValue); 610 return; 611 } 612 } 613 614 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_X, 615 child.getTranslationX(), newEndValue); 616 Interpolator customInterpolator = properties.getCustomInterpolator(child, 617 View.TRANSLATION_X); 618 Interpolator interpolator = customInterpolator != null ? customInterpolator 619 : Interpolators.FAST_OUT_SLOW_IN; 620 animator.setInterpolator(interpolator); 621 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); 622 animator.setDuration(newDuration); 623 if (properties.delay > 0 && (previousAnimator == null 624 || previousAnimator.getAnimatedFraction() == 0)) { 625 animator.setStartDelay(properties.delay); 626 } 627 AnimatorListenerAdapter listener = properties.getAnimationFinishListener( 628 View.TRANSLATION_X); 629 if (listener != null) { 630 animator.addListener(listener); 631 } 632 // remove the tag when the animation is finished 633 animator.addListener(new AnimatorListenerAdapter() { 634 @Override 635 public void onAnimationEnd(Animator animation) { 636 child.setTag(TAG_ANIMATOR_TRANSLATION_X, null); 637 child.setTag(TAG_START_TRANSLATION_X, null); 638 child.setTag(TAG_END_TRANSLATION_X, null); 639 } 640 }); 641 startAnimator(animator, listener); 642 child.setTag(TAG_ANIMATOR_TRANSLATION_X, animator); 643 child.setTag(TAG_START_TRANSLATION_X, child.getTranslationX()); 644 child.setTag(TAG_END_TRANSLATION_X, newEndValue); 645 } 646 updateAnimationY(View view)647 private void updateAnimationY(View view) { 648 startYTranslationAnimation(view, NO_NEW_ANIMATIONS); 649 } 650 startYTranslationAnimation(final View child, AnimationProperties properties)651 private void startYTranslationAnimation(final View child, AnimationProperties properties) { 652 Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_Y); 653 Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_Y); 654 float newEndValue = this.mYTranslation; 655 if (previousEndValue != null && previousEndValue == newEndValue) { 656 return; 657 } 658 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); 659 AnimationFilter filter = properties.getAnimationFilter(); 660 if (!filter.animateY) { 661 // just a local update was performed 662 if (previousAnimator != null) { 663 // we need to increase all animation keyframes of the previous animator by the 664 // relative change to the end value 665 PropertyValuesHolder[] values = previousAnimator.getValues(); 666 float relativeDiff = newEndValue - previousEndValue; 667 float newStartValue = previousStartValue + relativeDiff; 668 values[0].setFloatValues(newStartValue, newEndValue); 669 child.setTag(TAG_START_TRANSLATION_Y, newStartValue); 670 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 671 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 672 return; 673 } else { 674 // no new animation needed, let's just apply the value 675 child.setTranslationY(newEndValue); 676 return; 677 } 678 } 679 680 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, 681 child.getTranslationY(), newEndValue); 682 Interpolator customInterpolator = properties.getCustomInterpolator(child, 683 View.TRANSLATION_Y); 684 Interpolator interpolator = customInterpolator != null ? customInterpolator 685 : Interpolators.FAST_OUT_SLOW_IN; 686 animator.setInterpolator(interpolator); 687 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); 688 animator.setDuration(newDuration); 689 if (properties.delay > 0 && (previousAnimator == null 690 || previousAnimator.getAnimatedFraction() == 0)) { 691 animator.setStartDelay(properties.delay); 692 } 693 AnimatorListenerAdapter listener = properties.getAnimationFinishListener( 694 View.TRANSLATION_Y); 695 if (listener != null) { 696 animator.addListener(listener); 697 } 698 // remove the tag when the animation is finished 699 animator.addListener(new AnimatorListenerAdapter() { 700 @Override 701 public void onAnimationEnd(Animator animation) { 702 HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(child, false); 703 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); 704 child.setTag(TAG_START_TRANSLATION_Y, null); 705 child.setTag(TAG_END_TRANSLATION_Y, null); 706 onYTranslationAnimationFinished(child); 707 } 708 }); 709 startAnimator(animator, listener); 710 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); 711 child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY()); 712 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 713 } 714 onYTranslationAnimationFinished(View view)715 protected void onYTranslationAnimationFinished(View view) { 716 if (hidden && !gone) { 717 view.setVisibility(View.INVISIBLE); 718 } 719 } 720 startAnimator(Animator animator, AnimatorListenerAdapter listener)721 public static void startAnimator(Animator animator, AnimatorListenerAdapter listener) { 722 if (listener != null) { 723 // Even if there's a delay we'd want to notify it of the start immediately. 724 listener.onAnimationStart(animator); 725 } 726 animator.start(); 727 } 728 getChildTag(View child, int tag)729 public static <T> T getChildTag(View child, int tag) { 730 return (T) child.getTag(tag); 731 } 732 abortAnimation(View child, int animatorTag)733 protected void abortAnimation(View child, int animatorTag) { 734 Animator previousAnimator = getChildTag(child, animatorTag); 735 if (previousAnimator != null) { 736 previousAnimator.cancel(); 737 } 738 } 739 740 /** 741 * Cancel the previous animator and get the duration of the new animation. 742 * 743 * @param duration the new duration 744 * @param previousAnimator the animator which was running before 745 * @return the new duration 746 */ cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator)747 public static long cancelAnimatorAndGetNewDuration(long duration, 748 ValueAnimator previousAnimator) { 749 long newDuration = duration; 750 if (previousAnimator != null) { 751 // We take either the desired length of the new animation or the remaining time of 752 // the previous animator, whichever is longer. 753 newDuration = Math.max(previousAnimator.getDuration() 754 - previousAnimator.getCurrentPlayTime(), newDuration); 755 previousAnimator.cancel(); 756 } 757 return newDuration; 758 } 759 760 /** 761 * Get the end value of the xTranslation animation running on a view or the xTranslation 762 * if no animation is running. 763 */ getFinalTranslationX(View view)764 public static float getFinalTranslationX(View view) { 765 if (view == null) { 766 return 0; 767 } 768 ValueAnimator xAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_X); 769 if (xAnimator == null) { 770 return view.getTranslationX(); 771 } else { 772 return getChildTag(view, TAG_END_TRANSLATION_X); 773 } 774 } 775 776 /** 777 * Get the end value of the yTranslation animation running on a view or the yTranslation 778 * if no animation is running. 779 */ getFinalTranslationY(View view)780 public static float getFinalTranslationY(View view) { 781 if (view == null) { 782 return 0; 783 } 784 ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y); 785 if (yAnimator == null) { 786 return view.getTranslationY(); 787 } else { 788 return getChildTag(view, TAG_END_TRANSLATION_Y); 789 } 790 } 791 792 /** 793 * Get the end value of the zTranslation animation running on a view or the zTranslation 794 * if no animation is running. 795 */ getFinalTranslationZ(View view)796 public static float getFinalTranslationZ(View view) { 797 if (view == null) { 798 return 0; 799 } 800 ValueAnimator zAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Z); 801 if (zAnimator == null) { 802 return view.getTranslationZ(); 803 } else { 804 return getChildTag(view, TAG_END_TRANSLATION_Z); 805 } 806 } 807 isAnimatingY(View child)808 public static boolean isAnimatingY(View child) { 809 return getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null; 810 } 811 cancelAnimations(View view)812 public void cancelAnimations(View view) { 813 Animator animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_X); 814 if (animator != null) { 815 animator.cancel(); 816 } 817 animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y); 818 if (animator != null) { 819 animator.cancel(); 820 } 821 animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Z); 822 if (animator != null) { 823 animator.cancel(); 824 } 825 animator = getChildTag(view, TAG_ANIMATOR_ALPHA); 826 if (animator != null) { 827 animator.cancel(); 828 } 829 } 830 831 @Override dump(PrintWriter pw, String[] args)832 public void dump(PrintWriter pw, String[] args) { 833 StringBuilder result = new StringBuilder(); 834 result.append("ViewState { "); 835 836 boolean first = true; 837 Class currentClass = this.getClass(); 838 while (currentClass != null) { 839 Field[] fields = currentClass.getDeclaredFields(); 840 // Print field names paired with their values 841 for (Field field : fields) { 842 int modifiers = field.getModifiers(); 843 if (Modifier.isStatic(modifiers) || field.isSynthetic() 844 || Modifier.isTransient(modifiers)) { 845 continue; 846 } 847 if (!first) { 848 result.append(", "); 849 } 850 try { 851 result.append(field.getName()); 852 result.append(": "); 853 //requires access to private field: 854 field.setAccessible(true); 855 result.append(field.get(this)); 856 } catch (IllegalAccessException ex) { 857 } 858 first = false; 859 } 860 currentClass = currentClass.getSuperclass(); 861 } 862 result.append(" }"); 863 pw.print(result); 864 } 865 } 866