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