KeyButtonRipple.java revision f6829a0a618b4523619ec53c996b04d67e3186b9
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.policy; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.content.Context; 23import android.graphics.Canvas; 24import android.graphics.CanvasProperty; 25import android.graphics.ColorFilter; 26import android.graphics.Paint; 27import android.graphics.PixelFormat; 28import android.graphics.drawable.Drawable; 29import android.view.DisplayListCanvas; 30import android.view.RenderNodeAnimator; 31import android.view.View; 32import android.view.animation.Interpolator; 33 34import com.android.systemui.R; 35import com.android.systemui.statusbar.phone.PhoneStatusBar; 36 37import java.util.ArrayList; 38import java.util.HashSet; 39 40public class KeyButtonRipple extends Drawable { 41 42 private static final float GLOW_MAX_SCALE_FACTOR = 1.35f; 43 private static final float GLOW_MAX_ALPHA = 0.2f; 44 private static final int ANIMATION_DURATION_SCALE = 350; 45 private static final int ANIMATION_DURATION_FADE = 450; 46 47 private Paint mRipplePaint; 48 private CanvasProperty<Float> mLeftProp; 49 private CanvasProperty<Float> mTopProp; 50 private CanvasProperty<Float> mRightProp; 51 private CanvasProperty<Float> mBottomProp; 52 private CanvasProperty<Float> mRxProp; 53 private CanvasProperty<Float> mRyProp; 54 private CanvasProperty<Paint> mPaintProp; 55 private float mGlowAlpha = 0f; 56 private float mGlowScale = 1f; 57 private boolean mPressed; 58 private boolean mDrawingHardwareGlow; 59 private int mMaxWidth; 60 61 private final Interpolator mInterpolator = new LogInterpolator(); 62 private final Interpolator mAlphaExitInterpolator = PhoneStatusBar.ALPHA_OUT; 63 private boolean mSupportHardware; 64 private final View mTargetView; 65 66 private final HashSet<Animator> mRunningAnimations = new HashSet<>(); 67 private final ArrayList<Animator> mTmpArray = new ArrayList<>(); 68 69 public KeyButtonRipple(Context ctx, View targetView) { 70 mMaxWidth = ctx.getResources().getDimensionPixelSize(R.dimen.key_button_ripple_max_width); 71 mTargetView = targetView; 72 } 73 74 private Paint getRipplePaint() { 75 if (mRipplePaint == null) { 76 mRipplePaint = new Paint(); 77 mRipplePaint.setAntiAlias(true); 78 mRipplePaint.setColor(0xffffffff); 79 } 80 return mRipplePaint; 81 } 82 83 private void drawSoftware(Canvas canvas) { 84 if (mGlowAlpha > 0f) { 85 final Paint p = getRipplePaint(); 86 p.setAlpha((int)(mGlowAlpha * 255f)); 87 88 final float w = getBounds().width(); 89 final float h = getBounds().height(); 90 final boolean horizontal = w > h; 91 final float diameter = getRippleSize() * mGlowScale; 92 final float radius = diameter * .5f; 93 final float cx = w * .5f; 94 final float cy = h * .5f; 95 final float rx = horizontal ? radius : cx; 96 final float ry = horizontal ? cy : radius; 97 final float corner = horizontal ? cy : cx; 98 99 canvas.drawRoundRect(cx - rx, cy - ry, 100 cx + rx, cy + ry, 101 corner, corner, p); 102 } 103 } 104 105 @Override 106 public void draw(Canvas canvas) { 107 mSupportHardware = canvas.isHardwareAccelerated(); 108 if (mSupportHardware) { 109 drawHardware((DisplayListCanvas) canvas); 110 } else { 111 drawSoftware(canvas); 112 } 113 } 114 115 @Override 116 public void setAlpha(int alpha) { 117 // Not supported. 118 } 119 120 @Override 121 public void setColorFilter(ColorFilter colorFilter) { 122 // Not supported. 123 } 124 125 @Override 126 public int getOpacity() { 127 return PixelFormat.TRANSLUCENT; 128 } 129 130 private boolean isHorizontal() { 131 return getBounds().width() > getBounds().height(); 132 } 133 134 private void drawHardware(DisplayListCanvas c) { 135 if (mDrawingHardwareGlow) { 136 c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp, 137 mPaintProp); 138 } 139 } 140 141 public float getGlowAlpha() { 142 return mGlowAlpha; 143 } 144 145 public void setGlowAlpha(float x) { 146 mGlowAlpha = x; 147 invalidateSelf(); 148 } 149 150 public float getGlowScale() { 151 return mGlowScale; 152 } 153 154 public void setGlowScale(float x) { 155 mGlowScale = x; 156 invalidateSelf(); 157 } 158 159 @Override 160 protected boolean onStateChange(int[] state) { 161 boolean pressed = false; 162 for (int i = 0; i < state.length; i++) { 163 if (state[i] == android.R.attr.state_pressed) { 164 pressed = true; 165 break; 166 } 167 } 168 if (pressed != mPressed) { 169 setPressed(pressed); 170 mPressed = pressed; 171 return true; 172 } else { 173 return false; 174 } 175 } 176 177 @Override 178 public void jumpToCurrentState() { 179 cancelAnimations(); 180 } 181 182 @Override 183 public boolean isStateful() { 184 return true; 185 } 186 187 public void setPressed(boolean pressed) { 188 if (mSupportHardware) { 189 setPressedHardware(pressed); 190 } else { 191 setPressedSoftware(pressed); 192 } 193 } 194 195 private void cancelAnimations() { 196 mTmpArray.addAll(mRunningAnimations); 197 int size = mTmpArray.size(); 198 for (int i = 0; i < size; i++) { 199 Animator a = mTmpArray.get(i); 200 a.cancel(); 201 } 202 mTmpArray.clear(); 203 mRunningAnimations.clear(); 204 } 205 206 private void setPressedSoftware(boolean pressed) { 207 if (pressed) { 208 enterSoftware(); 209 } else { 210 exitSoftware(); 211 } 212 } 213 214 private void enterSoftware() { 215 cancelAnimations(); 216 mGlowAlpha = GLOW_MAX_ALPHA; 217 ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale", 218 0f, GLOW_MAX_SCALE_FACTOR); 219 scaleAnimator.setInterpolator(mInterpolator); 220 scaleAnimator.setDuration(ANIMATION_DURATION_SCALE); 221 scaleAnimator.addListener(mAnimatorListener); 222 scaleAnimator.start(); 223 mRunningAnimations.add(scaleAnimator); 224 } 225 226 private void exitSoftware() { 227 ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f); 228 alphaAnimator.setInterpolator(mAlphaExitInterpolator); 229 alphaAnimator.setDuration(ANIMATION_DURATION_FADE); 230 alphaAnimator.addListener(mAnimatorListener); 231 alphaAnimator.start(); 232 mRunningAnimations.add(alphaAnimator); 233 } 234 235 private void setPressedHardware(boolean pressed) { 236 if (pressed) { 237 enterHardware(); 238 } else { 239 exitHardware(); 240 } 241 } 242 243 /** 244 * Sets the left/top property for the round rect to {@code prop} depending on whether we are 245 * horizontal or vertical mode. 246 */ 247 private void setExtendStart(CanvasProperty<Float> prop) { 248 if (isHorizontal()) { 249 mLeftProp = prop; 250 } else { 251 mTopProp = prop; 252 } 253 } 254 255 private CanvasProperty<Float> getExtendStart() { 256 return isHorizontal() ? mLeftProp : mTopProp; 257 } 258 259 /** 260 * Sets the right/bottom property for the round rect to {@code prop} depending on whether we are 261 * horizontal or vertical mode. 262 */ 263 private void setExtendEnd(CanvasProperty<Float> prop) { 264 if (isHorizontal()) { 265 mRightProp = prop; 266 } else { 267 mBottomProp = prop; 268 } 269 } 270 271 private CanvasProperty<Float> getExtendEnd() { 272 return isHorizontal() ? mRightProp : mBottomProp; 273 } 274 275 private int getExtendSize() { 276 return isHorizontal() ? getBounds().width() : getBounds().height(); 277 } 278 279 private int getRippleSize() { 280 int size = isHorizontal() ? getBounds().width() : getBounds().height(); 281 return Math.min(size, mMaxWidth); 282 } 283 284 private void enterHardware() { 285 cancelAnimations(); 286 mDrawingHardwareGlow = true; 287 setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); 288 final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(), 289 getExtendSize()/2 - GLOW_MAX_SCALE_FACTOR * getRippleSize()/2); 290 startAnim.setDuration(ANIMATION_DURATION_SCALE); 291 startAnim.setInterpolator(mInterpolator); 292 startAnim.addListener(mAnimatorListener); 293 startAnim.setTarget(mTargetView); 294 295 setExtendEnd(CanvasProperty.createFloat(getExtendSize() / 2)); 296 final RenderNodeAnimator endAnim = new RenderNodeAnimator(getExtendEnd(), 297 getExtendSize()/2 + GLOW_MAX_SCALE_FACTOR * getRippleSize()/2); 298 endAnim.setDuration(ANIMATION_DURATION_SCALE); 299 endAnim.setInterpolator(mInterpolator); 300 endAnim.addListener(mAnimatorListener); 301 endAnim.setTarget(mTargetView); 302 303 if (isHorizontal()) { 304 mTopProp = CanvasProperty.createFloat(0f); 305 mBottomProp = CanvasProperty.createFloat(getBounds().height()); 306 mRxProp = CanvasProperty.createFloat(getBounds().height()/2); 307 mRyProp = CanvasProperty.createFloat(getBounds().height()/2); 308 } else { 309 mLeftProp = CanvasProperty.createFloat(0f); 310 mRightProp = CanvasProperty.createFloat(getBounds().width()); 311 mRxProp = CanvasProperty.createFloat(getBounds().width()/2); 312 mRyProp = CanvasProperty.createFloat(getBounds().width()/2); 313 } 314 315 mGlowScale = GLOW_MAX_SCALE_FACTOR; 316 mGlowAlpha = GLOW_MAX_ALPHA; 317 mRipplePaint = getRipplePaint(); 318 mRipplePaint.setAlpha((int) (mGlowAlpha * 255)); 319 mPaintProp = CanvasProperty.createPaint(mRipplePaint); 320 321 startAnim.start(); 322 endAnim.start(); 323 mRunningAnimations.add(startAnim); 324 mRunningAnimations.add(endAnim); 325 326 invalidateSelf(); 327 } 328 329 private void exitHardware() { 330 mPaintProp = CanvasProperty.createPaint(getRipplePaint()); 331 final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp, 332 RenderNodeAnimator.PAINT_ALPHA, 0); 333 opacityAnim.setDuration(ANIMATION_DURATION_FADE); 334 opacityAnim.setInterpolator(mAlphaExitInterpolator); 335 opacityAnim.addListener(mAnimatorListener); 336 opacityAnim.setTarget(mTargetView); 337 338 opacityAnim.start(); 339 mRunningAnimations.add(opacityAnim); 340 341 invalidateSelf(); 342 } 343 344 private final AnimatorListenerAdapter mAnimatorListener = 345 new AnimatorListenerAdapter() { 346 @Override 347 public void onAnimationEnd(Animator animation) { 348 mRunningAnimations.remove(animation); 349 if (mRunningAnimations.isEmpty() && !mPressed) { 350 mDrawingHardwareGlow = false; 351 invalidateSelf(); 352 } 353 } 354 }; 355 356 /** 357 * Interpolator with a smooth log deceleration 358 */ 359 private static final class LogInterpolator implements Interpolator { 360 @Override 361 public float getInterpolation(float input) { 362 return 1 - (float) Math.pow(400, -input * 1.4); 363 } 364 } 365} 366