Home | History | Annotate | Download | only in policy
      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.HardwareCanvas;
     30 import android.view.RenderNodeAnimator;
     31 import android.view.View;
     32 import android.view.animation.Interpolator;
     33 
     34 import com.android.systemui.R;
     35 import com.android.systemui.statusbar.phone.PhoneStatusBar;
     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 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 
    106     @Override
    107     public void draw(Canvas canvas) {
    108         mSupportHardware = canvas.isHardwareAccelerated();
    109         if (mSupportHardware) {
    110             drawHardware((HardwareCanvas) canvas);
    111         } else {
    112             drawSoftware(canvas);
    113         }
    114     }
    115 
    116     @Override
    117     public void setAlpha(int alpha) {
    118         // Not supported.
    119     }
    120 
    121     @Override
    122     public void setColorFilter(ColorFilter cf) {
    123         // Not supported.
    124     }
    125 
    126     @Override
    127     public int getOpacity() {
    128         return PixelFormat.TRANSLUCENT;
    129     }
    130 
    131     private boolean isHorizontal() {
    132         return getBounds().width() > getBounds().height();
    133     }
    134 
    135     private void drawHardware(HardwareCanvas c) {
    136         if (mDrawingHardwareGlow) {
    137             c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp,
    138                     mPaintProp);
    139         }
    140     }
    141 
    142     public float getGlowAlpha() {
    143         return mGlowAlpha;
    144     }
    145 
    146     public void setGlowAlpha(float x) {
    147         mGlowAlpha = x;
    148         invalidateSelf();
    149     }
    150 
    151     public float getGlowScale() {
    152         return mGlowScale;
    153     }
    154 
    155     public void setGlowScale(float x) {
    156         mGlowScale = x;
    157         invalidateSelf();
    158     }
    159 
    160     @Override
    161     protected boolean onStateChange(int[] state) {
    162         boolean pressed = false;
    163         for (int i = 0; i < state.length; i++) {
    164             if (state[i] == android.R.attr.state_pressed) {
    165                 pressed = true;
    166                 break;
    167             }
    168         }
    169         if (pressed != mPressed) {
    170             setPressed(pressed);
    171             mPressed = pressed;
    172             return true;
    173         } else {
    174             return false;
    175         }
    176     }
    177 
    178     @Override
    179     public boolean isStateful() {
    180         return true;
    181     }
    182 
    183     public void setPressed(boolean pressed) {
    184         if (mSupportHardware) {
    185             setPressedHardware(pressed);
    186         } else {
    187             setPressedSoftware(pressed);
    188         }
    189     }
    190 
    191     private void cancelAnimations() {
    192         mTmpArray.addAll(mRunningAnimations);
    193         int size = mTmpArray.size();
    194         for (int i = 0; i < size; i++) {
    195             Animator a = mTmpArray.get(i);
    196             a.cancel();
    197         }
    198         mTmpArray.clear();
    199         mRunningAnimations.clear();
    200     }
    201 
    202     private void setPressedSoftware(boolean pressed) {
    203         if (pressed) {
    204             enterSoftware();
    205         } else {
    206             exitSoftware();
    207         }
    208     }
    209 
    210     private void enterSoftware() {
    211         cancelAnimations();
    212         mGlowAlpha = GLOW_MAX_ALPHA;
    213         ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale",
    214                 0f, GLOW_MAX_SCALE_FACTOR);
    215         scaleAnimator.setInterpolator(mInterpolator);
    216         scaleAnimator.setDuration(ANIMATION_DURATION_SCALE);
    217         scaleAnimator.addListener(mAnimatorListener);
    218         scaleAnimator.start();
    219         mRunningAnimations.add(scaleAnimator);
    220     }
    221 
    222     private void exitSoftware() {
    223         ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f);
    224         alphaAnimator.setInterpolator(mAlphaExitInterpolator);
    225         alphaAnimator.setDuration(ANIMATION_DURATION_FADE);
    226         alphaAnimator.addListener(mAnimatorListener);
    227         alphaAnimator.start();
    228         mRunningAnimations.add(alphaAnimator);
    229     }
    230 
    231     private void setPressedHardware(boolean pressed) {
    232         if (pressed) {
    233             enterHardware();
    234         } else {
    235             exitHardware();
    236         }
    237     }
    238 
    239     /**
    240      * Sets the left/top property for the round rect to {@code prop} depending on whether we are
    241      * horizontal or vertical mode.
    242      */
    243     private void setExtendStart(CanvasProperty<Float> prop) {
    244         if (isHorizontal()) {
    245             mLeftProp = prop;
    246         } else {
    247             mTopProp = prop;
    248         }
    249     }
    250 
    251     private CanvasProperty<Float> getExtendStart() {
    252         return isHorizontal() ? mLeftProp : mTopProp;
    253     }
    254 
    255     /**
    256      * Sets the right/bottom property for the round rect to {@code prop} depending on whether we are
    257      * horizontal or vertical mode.
    258      */
    259     private void setExtendEnd(CanvasProperty<Float> prop) {
    260         if (isHorizontal()) {
    261             mRightProp = prop;
    262         } else {
    263             mBottomProp = prop;
    264         }
    265     }
    266 
    267     private CanvasProperty<Float> getExtendEnd() {
    268         return isHorizontal() ? mRightProp : mBottomProp;
    269     }
    270 
    271     private int getExtendSize() {
    272         return isHorizontal() ? getBounds().width() : getBounds().height();
    273     }
    274 
    275     private int getRippleSize() {
    276         int size = isHorizontal() ? getBounds().width() : getBounds().height();
    277         return Math.min(size, mMaxWidth);
    278     }
    279 
    280     private void enterHardware() {
    281         cancelAnimations();
    282         mDrawingHardwareGlow = true;
    283         setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
    284         final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(),
    285                 getExtendSize()/2 - GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
    286         startAnim.setDuration(ANIMATION_DURATION_SCALE);
    287         startAnim.setInterpolator(mInterpolator);
    288         startAnim.addListener(mAnimatorListener);
    289         startAnim.setTarget(mTargetView);
    290 
    291         setExtendEnd(CanvasProperty.createFloat(getExtendSize() / 2));
    292         final RenderNodeAnimator endAnim = new RenderNodeAnimator(getExtendEnd(),
    293                 getExtendSize()/2 + GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
    294         endAnim.setDuration(ANIMATION_DURATION_SCALE);
    295         endAnim.setInterpolator(mInterpolator);
    296         endAnim.addListener(mAnimatorListener);
    297         endAnim.setTarget(mTargetView);
    298 
    299         if (isHorizontal()) {
    300             mTopProp = CanvasProperty.createFloat(0f);
    301             mBottomProp = CanvasProperty.createFloat(getBounds().height());
    302             mRxProp = CanvasProperty.createFloat(getBounds().height()/2);
    303             mRyProp = CanvasProperty.createFloat(getBounds().height()/2);
    304         } else {
    305             mLeftProp = CanvasProperty.createFloat(0f);
    306             mRightProp = CanvasProperty.createFloat(getBounds().width());
    307             mRxProp = CanvasProperty.createFloat(getBounds().width()/2);
    308             mRyProp = CanvasProperty.createFloat(getBounds().width()/2);
    309         }
    310 
    311         mGlowScale = GLOW_MAX_SCALE_FACTOR;
    312         mGlowAlpha = GLOW_MAX_ALPHA;
    313         mRipplePaint = getRipplePaint();
    314         mRipplePaint.setAlpha((int) (mGlowAlpha * 255));
    315         mPaintProp = CanvasProperty.createPaint(mRipplePaint);
    316 
    317         startAnim.start();
    318         endAnim.start();
    319         mRunningAnimations.add(startAnim);
    320         mRunningAnimations.add(endAnim);
    321 
    322         invalidateSelf();
    323     }
    324 
    325     private void exitHardware() {
    326         mPaintProp = CanvasProperty.createPaint(getRipplePaint());
    327         final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp,
    328                 RenderNodeAnimator.PAINT_ALPHA, 0);
    329         opacityAnim.setDuration(ANIMATION_DURATION_FADE);
    330         opacityAnim.setInterpolator(mAlphaExitInterpolator);
    331         opacityAnim.addListener(mAnimatorListener);
    332         opacityAnim.setTarget(mTargetView);
    333 
    334         opacityAnim.start();
    335         mRunningAnimations.add(opacityAnim);
    336 
    337         invalidateSelf();
    338     }
    339 
    340     private final AnimatorListenerAdapter mAnimatorListener =
    341             new AnimatorListenerAdapter() {
    342         @Override
    343         public void onAnimationEnd(Animator animation) {
    344             mRunningAnimations.remove(animation);
    345             if (mRunningAnimations.isEmpty() && !mPressed) {
    346                 mDrawingHardwareGlow = false;
    347                 invalidateSelf();
    348             }
    349         }
    350     };
    351 
    352     /**
    353      * Interpolator with a smooth log deceleration
    354      */
    355     private static final class LogInterpolator implements Interpolator {
    356         @Override
    357         public float getInterpolation(float input) {
    358             return 1 - (float) Math.pow(400, -input * 1.4);
    359         }
    360     }
    361 }
    362