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 SlashDrawable(Drawable d)67 public SlashDrawable(Drawable d) { 68 mDrawable = d; 69 } 70 71 @Override getIntrinsicHeight()72 public int getIntrinsicHeight() { 73 return mDrawable != null ? mDrawable.getIntrinsicHeight(): 0; 74 } 75 76 @Override getIntrinsicWidth()77 public int getIntrinsicWidth() { 78 return mDrawable != null ? mDrawable.getIntrinsicWidth(): 0; 79 } 80 81 @Override onBoundsChange(Rect bounds)82 protected void onBoundsChange(Rect bounds) { 83 super.onBoundsChange(bounds); 84 mDrawable.setBounds(bounds); 85 } 86 setDrawable(Drawable d)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 setRotation(float rotation)96 public void setRotation(float rotation) { 97 if (mRotation == rotation) return; 98 mRotation = rotation; 99 invalidateSelf(); 100 } 101 setAnimationEnabled(boolean enabled)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 setSlashed(boolean slashed)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 draw(@onNull Canvas canvas)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 scale(float frac, int width)178 private float scale(float frac, int width) { 179 return frac * width; 180 } 181 updateRect(float left, float top, float right, float bottom)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 setTint(@olorInt int tintColor)190 public void setTint(@ColorInt int tintColor) { 191 super.setTint(tintColor); 192 mDrawable.setTint(tintColor); 193 mPaint.setColor(tintColor); 194 } 195 196 @Override setTintList(@ullable ColorStateList tint)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 setDrawableTintList(@ullable ColorStateList tint)205 protected void setDrawableTintList(@Nullable ColorStateList tint) { 206 mDrawable.setTintList(tint); 207 } 208 209 @Override setTintMode(@onNull Mode tintMode)210 public void setTintMode(@NonNull Mode tintMode) { 211 mTintMode = tintMode; 212 super.setTintMode(tintMode); 213 mDrawable.setTintMode(tintMode); 214 } 215 216 @Override setAlpha(@ntRangefrom = 0, to = 255) int alpha)217 public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { 218 mDrawable.setAlpha(alpha); 219 mPaint.setAlpha(alpha); 220 } 221 222 @Override setColorFilter(@ullable ColorFilter colorFilter)223 public void setColorFilter(@Nullable ColorFilter colorFilter) { 224 mDrawable.setColorFilter(colorFilter); 225 mPaint.setColorFilter(colorFilter); 226 } 227 228 @Override getOpacity()229 public int getOpacity() { 230 return 255; 231 } 232 } 233