ActivatableNotificationView.java revision 3c3c3fc38c474924629aa591c98d6dc190ed4e83
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.util.AttributeSet; 25import android.view.MotionEvent; 26import android.view.View; 27import android.view.ViewConfiguration; 28import android.view.animation.AnimationUtils; 29import android.view.animation.Interpolator; 30import android.view.animation.PathInterpolator; 31 32import com.android.systemui.R; 33 34/** 35 * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} 36 * to implement dimming/activating on Keyguard for the double-tap gesture 37 */ 38public abstract class ActivatableNotificationView extends ExpandableOutlineView { 39 40 private static final long DOUBLETAP_TIMEOUT_MS = 1200; 41 private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220; 42 private static final int ACTIVATE_ANIMATION_LENGTH = 220; 43 44 private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR 45 = new PathInterpolator(0.6f, 0, 0.5f, 1); 46 private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR 47 = new PathInterpolator(0, 0, 0.5f, 1); 48 49 private boolean mDimmed; 50 51 private int mBgResId = com.android.internal.R.drawable.notification_quantum_bg; 52 private int mDimmedBgResId = com.android.internal.R.drawable.notification_quantum_bg_dim; 53 54 private int mBgTint = 0; 55 private int mDimmedBgTint = 0; 56 57 /** 58 * Flag to indicate that the notification has been touched once and the second touch will 59 * click it. 60 */ 61 private boolean mActivated; 62 63 private float mDownX; 64 private float mDownY; 65 private final float mTouchSlop; 66 67 private OnActivatedListener mOnActivatedListener; 68 69 private Interpolator mLinearOutSlowInInterpolator; 70 private Interpolator mFastOutSlowInInterpolator; 71 72 private NotificationBackgroundView mBackgroundNormal; 73 private NotificationBackgroundView mBackgroundDimmed; 74 private ObjectAnimator mBackgroundAnimator; 75 76 public ActivatableNotificationView(Context context, AttributeSet attrs) { 77 super(context, attrs); 78 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 79 mFastOutSlowInInterpolator = 80 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); 81 mLinearOutSlowInInterpolator = 82 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); 83 setClipChildren(false); 84 setClipToPadding(false); 85 } 86 87 @Override 88 protected void onFinishInflate() { 89 super.onFinishInflate(); 90 mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal); 91 mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed); 92 updateBackgroundResources(); 93 } 94 95 private final Runnable mTapTimeoutRunnable = new Runnable() { 96 @Override 97 public void run() { 98 makeInactive(); 99 } 100 }; 101 102 @Override 103 public boolean onTouchEvent(MotionEvent event) { 104 if (mDimmed) { 105 return handleTouchEventDimmed(event); 106 } else { 107 return super.onTouchEvent(event); 108 } 109 } 110 111 private boolean handleTouchEventDimmed(MotionEvent event) { 112 int action = event.getActionMasked(); 113 switch (action) { 114 case MotionEvent.ACTION_DOWN: 115 mDownX = event.getX(); 116 mDownY = event.getY(); 117 if (mDownY > getActualHeight()) { 118 return false; 119 } 120 break; 121 case MotionEvent.ACTION_MOVE: 122 if (!isWithinTouchSlop(event)) { 123 makeInactive(); 124 return false; 125 } 126 break; 127 case MotionEvent.ACTION_UP: 128 if (isWithinTouchSlop(event)) { 129 if (!mActivated) { 130 makeActive(); 131 postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS); 132 } else { 133 performClick(); 134 } 135 } else { 136 makeInactive(); 137 } 138 break; 139 case MotionEvent.ACTION_CANCEL: 140 makeInactive(); 141 break; 142 default: 143 break; 144 } 145 return true; 146 } 147 148 private void makeActive() { 149 startActivateAnimation(false /* reverse */); 150 mActivated = true; 151 if (mOnActivatedListener != null) { 152 mOnActivatedListener.onActivated(this); 153 } 154 } 155 156 private void startActivateAnimation(boolean reverse) { 157 int widthHalf = mBackgroundNormal.getWidth()/2; 158 int heightHalf = mBackgroundNormal.getActualHeight()/2; 159 float radius = (float) Math.sqrt(widthHalf*widthHalf + heightHalf*heightHalf); 160 ValueAnimator animator = 161 mBackgroundNormal.createRevealAnimator(widthHalf, heightHalf, 0, radius); 162 mBackgroundNormal.setVisibility(View.VISIBLE); 163 Interpolator interpolator; 164 Interpolator alphaInterpolator; 165 if (!reverse) { 166 interpolator = mLinearOutSlowInInterpolator; 167 alphaInterpolator = mLinearOutSlowInInterpolator; 168 } else { 169 interpolator = ACTIVATE_INVERSE_INTERPOLATOR; 170 alphaInterpolator = ACTIVATE_INVERSE_ALPHA_INTERPOLATOR; 171 } 172 animator.setInterpolator(interpolator); 173 animator.setDuration(ACTIVATE_ANIMATION_LENGTH); 174 if (reverse) { 175 mBackgroundNormal.setAlpha(1f); 176 animator.addListener(new AnimatorListenerAdapter() { 177 @Override 178 public void onAnimationEnd(Animator animation) { 179 mBackgroundNormal.setVisibility(View.INVISIBLE); 180 } 181 }); 182 animator.reverse(); 183 } else { 184 mBackgroundNormal.setAlpha(0.4f); 185 animator.start(); 186 } 187 mBackgroundNormal.animate() 188 .alpha(reverse ? 0f : 1f) 189 .setInterpolator(alphaInterpolator) 190 .setDuration(ACTIVATE_ANIMATION_LENGTH); 191 } 192 193 /** 194 * Cancels the hotspot and makes the notification inactive. 195 */ 196 private void makeInactive() { 197 if (mActivated) { 198 if (mDimmed) { 199 startActivateAnimation(true /* reverse */); 200 } 201 mActivated = false; 202 } 203 if (mOnActivatedListener != null) { 204 mOnActivatedListener.onActivationReset(this); 205 } 206 removeCallbacks(mTapTimeoutRunnable); 207 } 208 209 private boolean isWithinTouchSlop(MotionEvent event) { 210 return Math.abs(event.getX() - mDownX) < mTouchSlop 211 && Math.abs(event.getY() - mDownY) < mTouchSlop; 212 } 213 214 public void setDimmed(boolean dimmed, boolean fade) { 215 if (mDimmed != dimmed) { 216 mDimmed = dimmed; 217 if (fade) { 218 fadeBackground(); 219 } else { 220 updateBackground(); 221 } 222 } 223 } 224 225 /** 226 * Sets the resource id for the background of this notification. 227 * 228 * @param bgResId The background resource to use in normal state. 229 * @param dimmedBgResId The background resource to use in dimmed state. 230 */ 231 public void setBackgroundResourceIds(int bgResId, int bgTint, int dimmedBgResId, int dimmedTint) { 232 mBgResId = bgResId; 233 mBgTint = bgTint; 234 mDimmedBgResId = dimmedBgResId; 235 mDimmedBgTint = dimmedTint; 236 updateBackgroundResources(); 237 } 238 239 public void setBackgroundResourceIds(int bgResId, int dimmedBgResId) { 240 setBackgroundResourceIds(bgResId, 0, dimmedBgResId, 0); 241 } 242 243 private void fadeBackground() { 244 if (mDimmed) { 245 mBackgroundDimmed.setVisibility(View.VISIBLE); 246 } else { 247 mBackgroundNormal.setVisibility(View.VISIBLE); 248 } 249 float startAlpha = mDimmed ? 1f : 0; 250 float endAlpha = mDimmed ? 0 : 1f; 251 int duration = BACKGROUND_ANIMATION_LENGTH_MS; 252 // Check whether there is already a background animation running. 253 if (mBackgroundAnimator != null) { 254 startAlpha = (Float) mBackgroundAnimator.getAnimatedValue(); 255 duration = (int) mBackgroundAnimator.getCurrentPlayTime(); 256 mBackgroundAnimator.removeAllListeners(); 257 mBackgroundAnimator.cancel(); 258 if (duration <= 0) { 259 updateBackground(); 260 return; 261 } 262 } 263 mBackgroundNormal.setAlpha(startAlpha); 264 mBackgroundAnimator = 265 ObjectAnimator.ofFloat(mBackgroundNormal, View.ALPHA, startAlpha, endAlpha); 266 mBackgroundAnimator.setInterpolator(mFastOutSlowInInterpolator); 267 mBackgroundAnimator.setDuration(duration); 268 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 269 @Override 270 public void onAnimationEnd(Animator animation) { 271 if (mDimmed) { 272 mBackgroundNormal.setVisibility(View.INVISIBLE); 273 } else { 274 mBackgroundDimmed.setVisibility(View.INVISIBLE); 275 } 276 mBackgroundAnimator = null; 277 } 278 }); 279 mBackgroundAnimator.start(); 280 } 281 282 private void updateBackground() { 283 if (mDimmed) { 284 mBackgroundDimmed.setVisibility(View.VISIBLE); 285 mBackgroundNormal.setVisibility(View.INVISIBLE); 286 } else { 287 mBackgroundDimmed.setVisibility(View.INVISIBLE); 288 mBackgroundNormal.setVisibility(View.VISIBLE); 289 mBackgroundNormal.setAlpha(1f); 290 } 291 } 292 293 private void updateBackgroundResources() { 294 mBackgroundDimmed.setCustomBackground(mDimmedBgResId, mDimmedBgTint); 295 mBackgroundNormal.setCustomBackground(mBgResId, mBgTint); 296 } 297 298 @Override 299 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 300 super.onLayout(changed, left, top, right, bottom); 301 setPivotX(getWidth() / 2); 302 } 303 304 @Override 305 public void setActualHeight(int actualHeight, boolean notifyListeners) { 306 super.setActualHeight(actualHeight, notifyListeners); 307 setPivotY(actualHeight / 2); 308 mBackgroundNormal.setActualHeight(actualHeight); 309 mBackgroundDimmed.setActualHeight(actualHeight); 310 } 311 312 @Override 313 public void setClipTopAmount(int clipTopAmount) { 314 super.setClipTopAmount(clipTopAmount); 315 mBackgroundNormal.setClipTopAmount(clipTopAmount); 316 mBackgroundDimmed.setClipTopAmount(clipTopAmount); 317 } 318 319 public void setOnActivatedListener(OnActivatedListener onActivatedListener) { 320 mOnActivatedListener = onActivatedListener; 321 } 322 323 public interface OnActivatedListener { 324 void onActivated(View view); 325 void onActivationReset(View view); 326 } 327} 328