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