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