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