• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.systemui.statusbar.policy;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.content.Context;
23 import android.graphics.Canvas;
24 import android.graphics.CanvasProperty;
25 import android.graphics.ColorFilter;
26 import android.graphics.Paint;
27 import android.graphics.PixelFormat;
28 import android.graphics.drawable.Drawable;
29 import android.view.DisplayListCanvas;
30 import android.view.RenderNodeAnimator;
31 import android.view.View;
32 import android.view.animation.Interpolator;
33 
34 import com.android.systemui.Interpolators;
35 import com.android.systemui.R;
36 
37 import java.util.ArrayList;
38 import java.util.HashSet;
39 
40 public 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 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 
KeyButtonRipple(Context ctx, View targetView)68     public KeyButtonRipple(Context ctx, View targetView) {
69         mMaxWidth =  ctx.getResources().getDimensionPixelSize(R.dimen.key_button_ripple_max_width);
70         mTargetView = targetView;
71     }
72 
getRipplePaint()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 
drawSoftware(Canvas canvas)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 
104     @Override
draw(Canvas canvas)105     public void draw(Canvas canvas) {
106         mSupportHardware = canvas.isHardwareAccelerated();
107         if (mSupportHardware) {
108             drawHardware((DisplayListCanvas) canvas);
109         } else {
110             drawSoftware(canvas);
111         }
112     }
113 
114     @Override
setAlpha(int alpha)115     public void setAlpha(int alpha) {
116         // Not supported.
117     }
118 
119     @Override
setColorFilter(ColorFilter colorFilter)120     public void setColorFilter(ColorFilter colorFilter) {
121         // Not supported.
122     }
123 
124     @Override
getOpacity()125     public int getOpacity() {
126         return PixelFormat.TRANSLUCENT;
127     }
128 
isHorizontal()129     private boolean isHorizontal() {
130         return getBounds().width() > getBounds().height();
131     }
132 
drawHardware(DisplayListCanvas c)133     private void drawHardware(DisplayListCanvas c) {
134         if (mDrawingHardwareGlow) {
135             c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp,
136                     mPaintProp);
137         }
138     }
139 
getGlowAlpha()140     public float getGlowAlpha() {
141         return mGlowAlpha;
142     }
143 
setGlowAlpha(float x)144     public void setGlowAlpha(float x) {
145         mGlowAlpha = x;
146         invalidateSelf();
147     }
148 
getGlowScale()149     public float getGlowScale() {
150         return mGlowScale;
151     }
152 
setGlowScale(float x)153     public void setGlowScale(float x) {
154         mGlowScale = x;
155         invalidateSelf();
156     }
157 
158     @Override
onStateChange(int[] state)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
jumpToCurrentState()177     public void jumpToCurrentState() {
178         cancelAnimations();
179     }
180 
181     @Override
isStateful()182     public boolean isStateful() {
183         return true;
184     }
185 
setPressed(boolean pressed)186     public void setPressed(boolean pressed) {
187         if (mSupportHardware) {
188             setPressedHardware(pressed);
189         } else {
190             setPressedSoftware(pressed);
191         }
192     }
193 
cancelAnimations()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 
setPressedSoftware(boolean pressed)205     private void setPressedSoftware(boolean pressed) {
206         if (pressed) {
207             enterSoftware();
208         } else {
209             exitSoftware();
210         }
211     }
212 
enterSoftware()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 
exitSoftware()225     private void exitSoftware() {
226         ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f);
227         alphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
228         alphaAnimator.setDuration(ANIMATION_DURATION_FADE);
229         alphaAnimator.addListener(mAnimatorListener);
230         alphaAnimator.start();
231         mRunningAnimations.add(alphaAnimator);
232     }
233 
setPressedHardware(boolean pressed)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      */
setExtendStart(CanvasProperty<Float> prop)246     private void setExtendStart(CanvasProperty<Float> prop) {
247         if (isHorizontal()) {
248             mLeftProp = prop;
249         } else {
250             mTopProp = prop;
251         }
252     }
253 
getExtendStart()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      */
setExtendEnd(CanvasProperty<Float> prop)262     private void setExtendEnd(CanvasProperty<Float> prop) {
263         if (isHorizontal()) {
264             mRightProp = prop;
265         } else {
266             mBottomProp = prop;
267         }
268     }
269 
getExtendEnd()270     private CanvasProperty<Float> getExtendEnd() {
271         return isHorizontal() ? mRightProp : mBottomProp;
272     }
273 
getExtendSize()274     private int getExtendSize() {
275         return isHorizontal() ? getBounds().width() : getBounds().height();
276     }
277 
getRippleSize()278     private int getRippleSize() {
279         int size = isHorizontal() ? getBounds().width() : getBounds().height();
280         return Math.min(size, mMaxWidth);
281     }
282 
enterHardware()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 
exitHardware()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);
333         opacityAnim.setInterpolator(Interpolators.ALPHA_OUT);
334         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
getInterpolation(float input)360         public float getInterpolation(float input) {
361             return 1 - (float) Math.pow(400, -input * 1.4);
362         }
363     }
364 }
365