Home | History | Annotate | Download | only in qs
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package com.android.systemui.qs;
     16 
     17 import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
     18 
     19 import android.animation.ObjectAnimator;
     20 import android.animation.ValueAnimator;
     21 import android.annotation.ColorInt;
     22 import android.annotation.IntRange;
     23 import android.annotation.NonNull;
     24 import android.annotation.Nullable;
     25 import android.content.res.ColorStateList;
     26 import android.graphics.Canvas;
     27 import android.graphics.ColorFilter;
     28 import android.graphics.Matrix;
     29 import android.graphics.Paint;
     30 import android.graphics.Path;
     31 import android.graphics.Path.Direction;
     32 import android.graphics.PorterDuff.Mode;
     33 import android.graphics.Rect;
     34 import android.graphics.RectF;
     35 import android.graphics.drawable.Drawable;
     36 import android.util.FloatProperty;
     37 
     38 public class SlashDrawable extends Drawable {
     39 
     40     public static final float CORNER_RADIUS = 1f;
     41 
     42     private final Path mPath = new Path();
     43     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     44 
     45     // These values are derived in un-rotated (vertical) orientation
     46     private static final float SLASH_WIDTH = 1.8384776f;
     47     private static final float SLASH_HEIGHT = 28f;
     48     private static final float CENTER_X = 10.65f;
     49     private static final float CENTER_Y = 11.869239f;
     50     private static final float SCALE = 24f;
     51 
     52     // Bottom is derived during animation
     53     private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
     54     private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
     55     private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
     56     // Draw the slash washington-monument style; rotate to no-u-turn style
     57     private static final float DEFAULT_ROTATION = -45f;
     58 
     59     private Drawable mDrawable;
     60     private final RectF mSlashRect = new RectF(0, 0, 0, 0);
     61     private float mRotation;
     62     private boolean mSlashed;
     63     private Mode mTintMode;
     64     private ColorStateList mTintList;
     65     private boolean mAnimationEnabled = true;
     66 
     67     public SlashDrawable(Drawable d) {
     68         mDrawable = d;
     69     }
     70 
     71     @Override
     72     public int getIntrinsicHeight() {
     73         return mDrawable != null ? mDrawable.getIntrinsicHeight(): 0;
     74     }
     75 
     76     @Override
     77     public int getIntrinsicWidth() {
     78         return mDrawable != null ? mDrawable.getIntrinsicWidth(): 0;
     79     }
     80 
     81     @Override
     82     protected void onBoundsChange(Rect bounds) {
     83         super.onBoundsChange(bounds);
     84         mDrawable.setBounds(bounds);
     85     }
     86 
     87     public void setDrawable(Drawable d) {
     88         mDrawable = d;
     89         mDrawable.setCallback(getCallback());
     90         mDrawable.setBounds(getBounds());
     91         if (mTintMode != null) mDrawable.setTintMode(mTintMode);
     92         if (mTintList != null) mDrawable.setTintList(mTintList);
     93         invalidateSelf();
     94     }
     95 
     96     public void setRotation(float rotation) {
     97         if (mRotation == rotation) return;
     98         mRotation = rotation;
     99         invalidateSelf();
    100     }
    101 
    102     public void setAnimationEnabled(boolean enabled) {
    103         mAnimationEnabled = enabled;
    104     }
    105 
    106     // Animate this value on change
    107     private float mCurrentSlashLength;
    108     private final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") {
    109         @Override
    110         public void setValue(SlashDrawable object, float value) {
    111             object.mCurrentSlashLength = value;
    112         }
    113 
    114         @Override
    115         public Float get(SlashDrawable object) {
    116             return object.mCurrentSlashLength;
    117         }
    118     };
    119 
    120     public void setSlashed(boolean slashed) {
    121         if (mSlashed == slashed) return;
    122 
    123         mSlashed = slashed;
    124 
    125         final float end = mSlashed ? SLASH_HEIGHT / SCALE : 0f;
    126         final float start = mSlashed ? 0f : SLASH_HEIGHT / SCALE;
    127 
    128         if (mAnimationEnabled) {
    129             ObjectAnimator anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end);
    130             anim.addUpdateListener((ValueAnimator valueAnimator) -> invalidateSelf());
    131             anim.setDuration(QS_ANIM_LENGTH);
    132             anim.start();
    133         } else {
    134             mCurrentSlashLength = end;
    135             invalidateSelf();
    136         }
    137     }
    138 
    139     @Override
    140     public void draw(@NonNull Canvas canvas) {
    141         canvas.save();
    142         Matrix m = new Matrix();
    143         final int width = getBounds().width();
    144         final int height = getBounds().height();
    145         final float radiusX = scale(CORNER_RADIUS, width);
    146         final float radiusY = scale(CORNER_RADIUS, height);
    147         updateRect(
    148                 scale(LEFT, width),
    149                 scale(TOP, height),
    150                 scale(RIGHT, width),
    151                 scale(TOP + mCurrentSlashLength, height)
    152         );
    153 
    154         mPath.reset();
    155         // Draw the slash vertically
    156         mPath.addRoundRect(mSlashRect, radiusX, radiusY, Direction.CW);
    157         // Rotate -45 + desired rotation
    158         m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
    159         mPath.transform(m);
    160         canvas.drawPath(mPath, mPaint);
    161 
    162         // Rotate back to vertical
    163         m.setRotate(-mRotation - DEFAULT_ROTATION, width / 2, height / 2);
    164         mPath.transform(m);
    165 
    166         // Draw another rect right next to the first, for clipping
    167         m.setTranslate(mSlashRect.width(), 0);
    168         mPath.transform(m);
    169         mPath.addRoundRect(mSlashRect, 1.0f * width, 1.0f * height, Direction.CW);
    170         m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
    171         mPath.transform(m);
    172         canvas.clipOutPath(mPath);
    173 
    174         mDrawable.draw(canvas);
    175         canvas.restore();
    176     }
    177 
    178     private float scale(float frac, int width) {
    179         return frac * width;
    180     }
    181 
    182     private void updateRect(float left, float top, float right, float bottom) {
    183         mSlashRect.left = left;
    184         mSlashRect.top = top;
    185         mSlashRect.right = right;
    186         mSlashRect.bottom = bottom;
    187     }
    188 
    189     @Override
    190     public void setTint(@ColorInt int tintColor) {
    191         super.setTint(tintColor);
    192         mDrawable.setTint(tintColor);
    193         mPaint.setColor(tintColor);
    194     }
    195 
    196     @Override
    197     public void setTintList(@Nullable ColorStateList tint) {
    198         mTintList = tint;
    199         super.setTintList(tint);
    200         setDrawableTintList(tint);
    201         mPaint.setColor(tint.getDefaultColor());
    202         invalidateSelf();
    203     }
    204 
    205     protected void setDrawableTintList(@Nullable ColorStateList tint) {
    206         mDrawable.setTintList(tint);
    207     }
    208 
    209     @Override
    210     public void setTintMode(@NonNull Mode tintMode) {
    211         mTintMode = tintMode;
    212         super.setTintMode(tintMode);
    213         mDrawable.setTintMode(tintMode);
    214     }
    215 
    216     @Override
    217     public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
    218         mDrawable.setAlpha(alpha);
    219         mPaint.setAlpha(alpha);
    220     }
    221 
    222     @Override
    223     public void setColorFilter(@Nullable ColorFilter colorFilter) {
    224         mDrawable.setColorFilter(colorFilter);
    225         mPaint.setColorFilter(colorFilter);
    226     }
    227 
    228     @Override
    229     public int getOpacity() {
    230         return 255;
    231     }
    232 }
    233