1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.statusbar; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.ValueAnimator; 23 import android.content.Context; 24 import android.graphics.Canvas; 25 import android.graphics.RectF; 26 import android.util.AttributeSet; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.ViewAnimationUtils; 30 import android.view.ViewConfiguration; 31 import android.view.animation.AnimationUtils; 32 import android.view.animation.Interpolator; 33 import android.view.animation.LinearInterpolator; 34 import android.view.animation.PathInterpolator; 35 36 import com.android.systemui.R; 37 38 /** 39 * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} 40 * to implement dimming/activating on Keyguard for the double-tap gesture 41 */ 42 public abstract class ActivatableNotificationView extends ExpandableOutlineView { 43 44 private static final long DOUBLETAP_TIMEOUT_MS = 1200; 45 private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220; 46 private static final int ACTIVATE_ANIMATION_LENGTH = 220; 47 private static final int DARK_ANIMATION_LENGTH = 170; 48 49 /** 50 * The amount of width, which is kept in the end when performing a disappear animation (also 51 * the amount from which the horizontal appearing begins) 52 */ 53 private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f; 54 55 /** 56 * At which point from [0,1] does the horizontal collapse animation end (or start when 57 * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated. 58 */ 59 private static final float HORIZONTAL_ANIMATION_END = 0.2f; 60 61 /** 62 * At which point from [0,1] does the alpha animation end (or start when 63 * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated. 64 */ 65 private static final float ALPHA_ANIMATION_END = 0.0f; 66 67 /** 68 * At which point from [0,1] does the horizontal collapse animation start (or start when 69 * expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all. 70 */ 71 private static final float HORIZONTAL_ANIMATION_START = 1.0f; 72 73 /** 74 * At which point from [0,1] does the vertical collapse animation start (or end when 75 * expanding) 1.0 meaning that it starts immediately and 0.0 that it is animated at all. 76 */ 77 private static final float VERTICAL_ANIMATION_START = 1.0f; 78 79 /** 80 * Scale for the background to animate from when exiting dark mode. 81 */ 82 private static final float DARK_EXIT_SCALE_START = 0.93f; 83 84 private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR 85 = new PathInterpolator(0.6f, 0, 0.5f, 1); 86 private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR 87 = new PathInterpolator(0, 0, 0.5f, 1); 88 private final int mTintedRippleColor; 89 private final int mLowPriorityRippleColor; 90 protected final int mNormalRippleColor; 91 92 private boolean mDimmed; 93 private boolean mDark; 94 95 private int mBgTint = 0; 96 97 /** 98 * Flag to indicate that the notification has been touched once and the second touch will 99 * click it. 100 */ 101 private boolean mActivated; 102 103 private float mDownX; 104 private float mDownY; 105 private final float mTouchSlop; 106 107 private OnActivatedListener mOnActivatedListener; 108 109 private final Interpolator mLinearOutSlowInInterpolator; 110 protected final Interpolator mFastOutSlowInInterpolator; 111 private final Interpolator mSlowOutFastInInterpolator; 112 private final Interpolator mSlowOutLinearInInterpolator; 113 private final Interpolator mLinearInterpolator; 114 private Interpolator mCurrentAppearInterpolator; 115 private Interpolator mCurrentAlphaInterpolator; 116 117 private NotificationBackgroundView mBackgroundNormal; 118 private NotificationBackgroundView mBackgroundDimmed; 119 private ObjectAnimator mBackgroundAnimator; 120 private RectF mAppearAnimationRect = new RectF(); 121 private float mAnimationTranslationY; 122 private boolean mDrawingAppearAnimation; 123 private ValueAnimator mAppearAnimator; 124 private float mAppearAnimationFraction = -1.0f; 125 private float mAppearAnimationTranslation; 126 private boolean mShowingLegacyBackground; 127 private final int mLegacyColor; 128 private final int mNormalColor; 129 private final int mLowPriorityColor; 130 private boolean mIsBelowSpeedBump; 131 ActivatableNotificationView(Context context, AttributeSet attrs)132 public ActivatableNotificationView(Context context, AttributeSet attrs) { 133 super(context, attrs); 134 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 135 mFastOutSlowInInterpolator = 136 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); 137 mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f); 138 mLinearOutSlowInInterpolator = 139 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); 140 mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f); 141 mLinearInterpolator = new LinearInterpolator(); 142 setClipChildren(false); 143 setClipToPadding(false); 144 mLegacyColor = context.getColor(R.color.notification_legacy_background_color); 145 mNormalColor = context.getColor(R.color.notification_material_background_color); 146 mLowPriorityColor = context.getColor( 147 R.color.notification_material_background_low_priority_color); 148 mTintedRippleColor = context.getColor( 149 R.color.notification_ripple_tinted_color); 150 mLowPriorityRippleColor = context.getColor( 151 R.color.notification_ripple_color_low_priority); 152 mNormalRippleColor = context.getColor( 153 R.color.notification_ripple_untinted_color); 154 } 155 156 @Override onFinishInflate()157 protected void onFinishInflate() { 158 super.onFinishInflate(); 159 mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal); 160 mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed); 161 mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg); 162 mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim); 163 updateBackground(); 164 updateBackgroundTint(); 165 } 166 167 private final Runnable mTapTimeoutRunnable = new Runnable() { 168 @Override 169 public void run() { 170 makeInactive(true /* animate */); 171 } 172 }; 173 174 @Override onTouchEvent(MotionEvent event)175 public boolean onTouchEvent(MotionEvent event) { 176 if (mDimmed) { 177 return handleTouchEventDimmed(event); 178 } else { 179 return super.onTouchEvent(event); 180 } 181 } 182 183 @Override drawableHotspotChanged(float x, float y)184 public void drawableHotspotChanged(float x, float y) { 185 if (!mDimmed){ 186 mBackgroundNormal.drawableHotspotChanged(x, y); 187 } 188 } 189 190 @Override drawableStateChanged()191 protected void drawableStateChanged() { 192 super.drawableStateChanged(); 193 if (mDimmed) { 194 mBackgroundDimmed.setState(getDrawableState()); 195 } else { 196 mBackgroundNormal.setState(getDrawableState()); 197 } 198 } 199 handleTouchEventDimmed(MotionEvent event)200 private boolean handleTouchEventDimmed(MotionEvent event) { 201 int action = event.getActionMasked(); 202 switch (action) { 203 case MotionEvent.ACTION_DOWN: 204 mDownX = event.getX(); 205 mDownY = event.getY(); 206 if (mDownY > getActualHeight()) { 207 return false; 208 } 209 break; 210 case MotionEvent.ACTION_MOVE: 211 if (!isWithinTouchSlop(event)) { 212 makeInactive(true /* animate */); 213 return false; 214 } 215 break; 216 case MotionEvent.ACTION_UP: 217 if (isWithinTouchSlop(event)) { 218 if (!mActivated) { 219 makeActive(); 220 postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS); 221 } else { 222 boolean performed = performClick(); 223 if (performed) { 224 removeCallbacks(mTapTimeoutRunnable); 225 } 226 } 227 } else { 228 makeInactive(true /* animate */); 229 } 230 break; 231 case MotionEvent.ACTION_CANCEL: 232 makeInactive(true /* animate */); 233 break; 234 default: 235 break; 236 } 237 return true; 238 } 239 makeActive()240 private void makeActive() { 241 startActivateAnimation(false /* reverse */); 242 mActivated = true; 243 if (mOnActivatedListener != null) { 244 mOnActivatedListener.onActivated(this); 245 } 246 } 247 startActivateAnimation(boolean reverse)248 private void startActivateAnimation(boolean reverse) { 249 if (!isAttachedToWindow()) { 250 return; 251 } 252 int widthHalf = mBackgroundNormal.getWidth()/2; 253 int heightHalf = mBackgroundNormal.getActualHeight()/2; 254 float radius = (float) Math.sqrt(widthHalf*widthHalf + heightHalf*heightHalf); 255 Animator animator; 256 if (reverse) { 257 animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal, 258 widthHalf, heightHalf, radius, 0); 259 } else { 260 animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal, 261 widthHalf, heightHalf, 0, radius); 262 } 263 mBackgroundNormal.setVisibility(View.VISIBLE); 264 Interpolator interpolator; 265 Interpolator alphaInterpolator; 266 if (!reverse) { 267 interpolator = mLinearOutSlowInInterpolator; 268 alphaInterpolator = mLinearOutSlowInInterpolator; 269 } else { 270 interpolator = ACTIVATE_INVERSE_INTERPOLATOR; 271 alphaInterpolator = ACTIVATE_INVERSE_ALPHA_INTERPOLATOR; 272 } 273 animator.setInterpolator(interpolator); 274 animator.setDuration(ACTIVATE_ANIMATION_LENGTH); 275 if (reverse) { 276 mBackgroundNormal.setAlpha(1f); 277 animator.addListener(new AnimatorListenerAdapter() { 278 @Override 279 public void onAnimationEnd(Animator animation) { 280 if (mDimmed) { 281 mBackgroundNormal.setVisibility(View.INVISIBLE); 282 } 283 } 284 }); 285 animator.start(); 286 } else { 287 mBackgroundNormal.setAlpha(0.4f); 288 animator.start(); 289 } 290 mBackgroundNormal.animate() 291 .alpha(reverse ? 0f : 1f) 292 .setInterpolator(alphaInterpolator) 293 .setDuration(ACTIVATE_ANIMATION_LENGTH); 294 } 295 296 /** 297 * Cancels the hotspot and makes the notification inactive. 298 */ makeInactive(boolean animate)299 public void makeInactive(boolean animate) { 300 if (mActivated) { 301 if (mDimmed) { 302 if (animate) { 303 startActivateAnimation(true /* reverse */); 304 } else { 305 mBackgroundNormal.setVisibility(View.INVISIBLE); 306 } 307 } 308 mActivated = false; 309 } 310 if (mOnActivatedListener != null) { 311 mOnActivatedListener.onActivationReset(this); 312 } 313 removeCallbacks(mTapTimeoutRunnable); 314 } 315 isWithinTouchSlop(MotionEvent event)316 private boolean isWithinTouchSlop(MotionEvent event) { 317 return Math.abs(event.getX() - mDownX) < mTouchSlop 318 && Math.abs(event.getY() - mDownY) < mTouchSlop; 319 } 320 setDimmed(boolean dimmed, boolean fade)321 public void setDimmed(boolean dimmed, boolean fade) { 322 if (mDimmed != dimmed) { 323 mDimmed = dimmed; 324 if (fade) { 325 fadeDimmedBackground(); 326 } else { 327 updateBackground(); 328 } 329 } 330 } 331 setDark(boolean dark, boolean fade, long delay)332 public void setDark(boolean dark, boolean fade, long delay) { 333 super.setDark(dark, fade, delay); 334 if (mDark == dark) { 335 return; 336 } 337 mDark = dark; 338 if (!dark && fade) { 339 if (mActivated) { 340 mBackgroundDimmed.setVisibility(View.VISIBLE); 341 mBackgroundNormal.setVisibility(View.VISIBLE); 342 } else if (mDimmed) { 343 mBackgroundDimmed.setVisibility(View.VISIBLE); 344 mBackgroundNormal.setVisibility(View.INVISIBLE); 345 } else { 346 mBackgroundDimmed.setVisibility(View.INVISIBLE); 347 mBackgroundNormal.setVisibility(View.VISIBLE); 348 } 349 fadeInFromDark(delay); 350 } else { 351 updateBackground(); 352 } 353 } 354 setShowingLegacyBackground(boolean showing)355 public void setShowingLegacyBackground(boolean showing) { 356 mShowingLegacyBackground = showing; 357 updateBackgroundTint(); 358 } 359 360 @Override setBelowSpeedBump(boolean below)361 public void setBelowSpeedBump(boolean below) { 362 super.setBelowSpeedBump(below); 363 if (below != mIsBelowSpeedBump) { 364 mIsBelowSpeedBump = below; 365 updateBackgroundTint(); 366 } 367 } 368 369 /** 370 * Sets the tint color of the background 371 */ setTintColor(int color)372 public void setTintColor(int color) { 373 mBgTint = color; 374 updateBackgroundTint(); 375 } 376 updateBackgroundTint()377 private void updateBackgroundTint() { 378 int color = getBgColor(); 379 int rippleColor = getRippleColor(); 380 if (color == mNormalColor) { 381 // We don't need to tint a normal notification 382 color = 0; 383 } 384 mBackgroundDimmed.setTint(color); 385 mBackgroundNormal.setTint(color); 386 mBackgroundDimmed.setRippleColor(rippleColor); 387 mBackgroundNormal.setRippleColor(rippleColor); 388 } 389 390 /** 391 * Fades in the background when exiting dark mode. 392 */ fadeInFromDark(long delay)393 private void fadeInFromDark(long delay) { 394 final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal; 395 background.setAlpha(0f); 396 background.setPivotX(mBackgroundDimmed.getWidth() / 2f); 397 background.setPivotY(getActualHeight() / 2f); 398 background.setScaleX(DARK_EXIT_SCALE_START); 399 background.setScaleY(DARK_EXIT_SCALE_START); 400 background.animate() 401 .alpha(1f) 402 .scaleX(1f) 403 .scaleY(1f) 404 .setDuration(DARK_ANIMATION_LENGTH) 405 .setStartDelay(delay) 406 .setInterpolator(mLinearOutSlowInInterpolator) 407 .setListener(new AnimatorListenerAdapter() { 408 @Override 409 public void onAnimationCancel(Animator animation) { 410 // Jump state if we are cancelled 411 background.setScaleX(1f); 412 background.setScaleY(1f); 413 background.setAlpha(1f); 414 } 415 }) 416 .start(); 417 } 418 419 /** 420 * Fades the background when the dimmed state changes. 421 */ fadeDimmedBackground()422 private void fadeDimmedBackground() { 423 mBackgroundDimmed.animate().cancel(); 424 mBackgroundNormal.animate().cancel(); 425 if (mDimmed) { 426 mBackgroundDimmed.setVisibility(View.VISIBLE); 427 } else { 428 mBackgroundNormal.setVisibility(View.VISIBLE); 429 } 430 float startAlpha = mDimmed ? 1f : 0; 431 float endAlpha = mDimmed ? 0 : 1f; 432 int duration = BACKGROUND_ANIMATION_LENGTH_MS; 433 // Check whether there is already a background animation running. 434 if (mBackgroundAnimator != null) { 435 startAlpha = (Float) mBackgroundAnimator.getAnimatedValue(); 436 duration = (int) mBackgroundAnimator.getCurrentPlayTime(); 437 mBackgroundAnimator.removeAllListeners(); 438 mBackgroundAnimator.cancel(); 439 if (duration <= 0) { 440 updateBackground(); 441 return; 442 } 443 } 444 mBackgroundNormal.setAlpha(startAlpha); 445 mBackgroundAnimator = 446 ObjectAnimator.ofFloat(mBackgroundNormal, View.ALPHA, startAlpha, endAlpha); 447 mBackgroundAnimator.setInterpolator(mFastOutSlowInInterpolator); 448 mBackgroundAnimator.setDuration(duration); 449 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 450 @Override 451 public void onAnimationEnd(Animator animation) { 452 if (mDimmed) { 453 mBackgroundNormal.setVisibility(View.INVISIBLE); 454 } else { 455 mBackgroundDimmed.setVisibility(View.INVISIBLE); 456 } 457 mBackgroundAnimator = null; 458 } 459 }); 460 mBackgroundAnimator.start(); 461 } 462 updateBackground()463 private void updateBackground() { 464 cancelFadeAnimations(); 465 if (mDark) { 466 mBackgroundDimmed.setVisibility(View.INVISIBLE); 467 mBackgroundNormal.setVisibility(View.INVISIBLE); 468 } else if (mDimmed) { 469 mBackgroundDimmed.setVisibility(View.VISIBLE); 470 mBackgroundNormal.setVisibility(View.INVISIBLE); 471 } else { 472 mBackgroundDimmed.setVisibility(View.INVISIBLE); 473 mBackgroundNormal.setVisibility(View.VISIBLE); 474 mBackgroundNormal.setAlpha(1f); 475 removeCallbacks(mTapTimeoutRunnable); 476 } 477 } 478 cancelFadeAnimations()479 private void cancelFadeAnimations() { 480 if (mBackgroundAnimator != null) { 481 mBackgroundAnimator.cancel(); 482 } 483 mBackgroundDimmed.animate().cancel(); 484 mBackgroundNormal.animate().cancel(); 485 } 486 487 @Override onLayout(boolean changed, int left, int top, int right, int bottom)488 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 489 super.onLayout(changed, left, top, right, bottom); 490 setPivotX(getWidth() / 2); 491 } 492 493 @Override setActualHeight(int actualHeight, boolean notifyListeners)494 public void setActualHeight(int actualHeight, boolean notifyListeners) { 495 super.setActualHeight(actualHeight, notifyListeners); 496 setPivotY(actualHeight / 2); 497 mBackgroundNormal.setActualHeight(actualHeight); 498 mBackgroundDimmed.setActualHeight(actualHeight); 499 } 500 501 @Override setClipTopAmount(int clipTopAmount)502 public void setClipTopAmount(int clipTopAmount) { 503 super.setClipTopAmount(clipTopAmount); 504 mBackgroundNormal.setClipTopAmount(clipTopAmount); 505 mBackgroundDimmed.setClipTopAmount(clipTopAmount); 506 } 507 508 @Override performRemoveAnimation(long duration, float translationDirection, Runnable onFinishedRunnable)509 public void performRemoveAnimation(long duration, float translationDirection, 510 Runnable onFinishedRunnable) { 511 enableAppearDrawing(true); 512 if (mDrawingAppearAnimation) { 513 startAppearAnimation(false /* isAppearing */, translationDirection, 514 0, duration, onFinishedRunnable); 515 } else if (onFinishedRunnable != null) { 516 onFinishedRunnable.run(); 517 } 518 } 519 520 @Override performAddAnimation(long delay, long duration)521 public void performAddAnimation(long delay, long duration) { 522 enableAppearDrawing(true); 523 if (mDrawingAppearAnimation) { 524 startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null); 525 } 526 } 527 startAppearAnimation(boolean isAppearing, float translationDirection, long delay, long duration, final Runnable onFinishedRunnable)528 private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, 529 long duration, final Runnable onFinishedRunnable) { 530 cancelAppearAnimation(); 531 mAnimationTranslationY = translationDirection * getActualHeight(); 532 if (mAppearAnimationFraction == -1.0f) { 533 // not initialized yet, we start anew 534 if (isAppearing) { 535 mAppearAnimationFraction = 0.0f; 536 mAppearAnimationTranslation = mAnimationTranslationY; 537 } else { 538 mAppearAnimationFraction = 1.0f; 539 mAppearAnimationTranslation = 0; 540 } 541 } 542 543 float targetValue; 544 if (isAppearing) { 545 mCurrentAppearInterpolator = mSlowOutFastInInterpolator; 546 mCurrentAlphaInterpolator = mLinearOutSlowInInterpolator; 547 targetValue = 1.0f; 548 } else { 549 mCurrentAppearInterpolator = mFastOutSlowInInterpolator; 550 mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator; 551 targetValue = 0.0f; 552 } 553 mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, 554 targetValue); 555 mAppearAnimator.setInterpolator(mLinearInterpolator); 556 mAppearAnimator.setDuration( 557 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); 558 mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 559 @Override 560 public void onAnimationUpdate(ValueAnimator animation) { 561 mAppearAnimationFraction = (float) animation.getAnimatedValue(); 562 updateAppearAnimationAlpha(); 563 updateAppearRect(); 564 invalidate(); 565 } 566 }); 567 if (delay > 0) { 568 // we need to apply the initial state already to avoid drawn frames in the wrong state 569 updateAppearAnimationAlpha(); 570 updateAppearRect(); 571 mAppearAnimator.setStartDelay(delay); 572 } 573 mAppearAnimator.addListener(new AnimatorListenerAdapter() { 574 private boolean mWasCancelled; 575 576 @Override 577 public void onAnimationEnd(Animator animation) { 578 if (onFinishedRunnable != null) { 579 onFinishedRunnable.run(); 580 } 581 if (!mWasCancelled) { 582 mAppearAnimationFraction = -1; 583 setOutlineRect(null); 584 enableAppearDrawing(false); 585 } 586 } 587 588 @Override 589 public void onAnimationStart(Animator animation) { 590 mWasCancelled = false; 591 } 592 593 @Override 594 public void onAnimationCancel(Animator animation) { 595 mWasCancelled = true; 596 } 597 }); 598 mAppearAnimator.start(); 599 } 600 cancelAppearAnimation()601 private void cancelAppearAnimation() { 602 if (mAppearAnimator != null) { 603 mAppearAnimator.cancel(); 604 } 605 } 606 cancelAppearDrawing()607 public void cancelAppearDrawing() { 608 cancelAppearAnimation(); 609 enableAppearDrawing(false); 610 } 611 updateAppearRect()612 private void updateAppearRect() { 613 float inverseFraction = (1.0f - mAppearAnimationFraction); 614 float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction); 615 float translateYTotalAmount = translationFraction * mAnimationTranslationY; 616 mAppearAnimationTranslation = translateYTotalAmount; 617 618 // handle width animation 619 float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START)) 620 / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END); 621 widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction)); 622 widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction); 623 float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) * 624 widthFraction); 625 float right = getWidth() - left; 626 627 // handle top animation 628 float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) / 629 VERTICAL_ANIMATION_START; 630 heightFraction = Math.max(0.0f, heightFraction); 631 heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction); 632 633 float top; 634 float bottom; 635 final int actualHeight = getActualHeight(); 636 if (mAnimationTranslationY > 0.0f) { 637 bottom = actualHeight - heightFraction * mAnimationTranslationY * 0.1f 638 - translateYTotalAmount; 639 top = bottom * heightFraction; 640 } else { 641 top = heightFraction * (actualHeight + mAnimationTranslationY) * 0.1f - 642 translateYTotalAmount; 643 bottom = actualHeight * (1 - heightFraction) + top * heightFraction; 644 } 645 mAppearAnimationRect.set(left, top, right, bottom); 646 setOutlineRect(left, top + mAppearAnimationTranslation, right, 647 bottom + mAppearAnimationTranslation); 648 } 649 updateAppearAnimationAlpha()650 private void updateAppearAnimationAlpha() { 651 float contentAlphaProgress = mAppearAnimationFraction; 652 contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END); 653 contentAlphaProgress = Math.min(1.0f, contentAlphaProgress); 654 contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress); 655 setContentAlpha(contentAlphaProgress); 656 } 657 setContentAlpha(float contentAlpha)658 private void setContentAlpha(float contentAlpha) { 659 View contentView = getContentView(); 660 if (contentView.hasOverlappingRendering()) { 661 int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE 662 : LAYER_TYPE_HARDWARE; 663 int currentLayerType = contentView.getLayerType(); 664 if (currentLayerType != layerType) { 665 contentView.setLayerType(layerType, null); 666 } 667 } 668 contentView.setAlpha(contentAlpha); 669 } 670 getContentView()671 protected abstract View getContentView(); 672 getBgColor()673 private int getBgColor() { 674 if (mBgTint != 0) { 675 return mBgTint; 676 } else if (mShowingLegacyBackground) { 677 return mLegacyColor; 678 } else if (mIsBelowSpeedBump) { 679 return mLowPriorityColor; 680 } else { 681 return mNormalColor; 682 } 683 } 684 getRippleColor()685 protected int getRippleColor() { 686 if (mBgTint != 0) { 687 return mTintedRippleColor; 688 } else if (mShowingLegacyBackground) { 689 return mTintedRippleColor; 690 } else if (mIsBelowSpeedBump) { 691 return mLowPriorityRippleColor; 692 } else { 693 return mNormalRippleColor; 694 } 695 } 696 697 /** 698 * When we draw the appear animation, we render the view in a bitmap and render this bitmap 699 * as a shader of a rect. This call creates the Bitmap and switches the drawing mode, 700 * such that the normal drawing of the views does not happen anymore. 701 * 702 * @param enable Should it be enabled. 703 */ enableAppearDrawing(boolean enable)704 private void enableAppearDrawing(boolean enable) { 705 if (enable != mDrawingAppearAnimation) { 706 mDrawingAppearAnimation = enable; 707 if (!enable) { 708 setContentAlpha(1.0f); 709 } 710 invalidate(); 711 } 712 } 713 714 @Override dispatchDraw(Canvas canvas)715 protected void dispatchDraw(Canvas canvas) { 716 if (mDrawingAppearAnimation) { 717 canvas.save(); 718 canvas.translate(0, mAppearAnimationTranslation); 719 } 720 super.dispatchDraw(canvas); 721 if (mDrawingAppearAnimation) { 722 canvas.restore(); 723 } 724 } 725 setOnActivatedListener(OnActivatedListener onActivatedListener)726 public void setOnActivatedListener(OnActivatedListener onActivatedListener) { 727 mOnActivatedListener = onActivatedListener; 728 } 729 reset()730 public void reset() { 731 setTintColor(0); 732 setShowingLegacyBackground(false); 733 setBelowSpeedBump(false); 734 } 735 736 public interface OnActivatedListener { onActivated(ActivatableNotificationView view)737 void onActivated(ActivatableNotificationView view); onActivationReset(ActivatableNotificationView view)738 void onActivationReset(ActivatableNotificationView view); 739 } 740 } 741