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