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 @Nullable 64 private Mode mTintMode; 65 @Nullable 66 private ColorStateList mTintList; 67 private boolean mAnimationEnabled = true; 68 SlashDrawable(Drawable d)69 public SlashDrawable(Drawable d) { 70 mDrawable = d; 71 } 72 73 @Override getIntrinsicHeight()74 public int getIntrinsicHeight() { 75 return mDrawable != null ? mDrawable.getIntrinsicHeight(): 0; 76 } 77 78 @Override getIntrinsicWidth()79 public int getIntrinsicWidth() { 80 return mDrawable != null ? mDrawable.getIntrinsicWidth(): 0; 81 } 82 83 @Override onBoundsChange(Rect bounds)84 protected void onBoundsChange(Rect bounds) { 85 super.onBoundsChange(bounds); 86 mDrawable.setBounds(bounds); 87 } 88 setDrawable(Drawable d)89 public void setDrawable(Drawable d) { 90 mDrawable = d; 91 mDrawable.setCallback(getCallback()); 92 mDrawable.setBounds(getBounds()); 93 if (mTintMode != null) mDrawable.setTintMode(mTintMode); 94 if (mTintList != null) mDrawable.setTintList(mTintList); 95 invalidateSelf(); 96 } 97 setRotation(float rotation)98 public void setRotation(float rotation) { 99 if (mRotation == rotation) return; 100 mRotation = rotation; 101 invalidateSelf(); 102 } 103 setAnimationEnabled(boolean enabled)104 public void setAnimationEnabled(boolean enabled) { 105 mAnimationEnabled = enabled; 106 } 107 108 // Animate this value on change 109 private float mCurrentSlashLength; 110 private final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") { 111 @Override 112 public void setValue(SlashDrawable object, float value) { 113 object.mCurrentSlashLength = value; 114 } 115 116 @Override 117 public Float get(SlashDrawable object) { 118 return object.mCurrentSlashLength; 119 } 120 }; 121 setSlashed(boolean slashed)122 public void setSlashed(boolean slashed) { 123 if (mSlashed == slashed) return; 124 125 mSlashed = slashed; 126 127 final float end = mSlashed ? SLASH_HEIGHT / SCALE : 0f; 128 final float start = mSlashed ? 0f : SLASH_HEIGHT / SCALE; 129 130 if (mAnimationEnabled) { 131 ObjectAnimator anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end); 132 anim.addUpdateListener((ValueAnimator valueAnimator) -> invalidateSelf()); 133 anim.setDuration(QS_ANIM_LENGTH); 134 anim.start(); 135 } else { 136 mCurrentSlashLength = end; 137 invalidateSelf(); 138 } 139 } 140 141 @Override draw(@onNull Canvas canvas)142 public void draw(@NonNull Canvas canvas) { 143 canvas.save(); 144 Matrix m = new Matrix(); 145 final int width = getBounds().width(); 146 final int height = getBounds().height(); 147 final float radiusX = scale(CORNER_RADIUS, width); 148 final float radiusY = scale(CORNER_RADIUS, height); 149 updateRect( 150 scale(LEFT, width), 151 scale(TOP, height), 152 scale(RIGHT, width), 153 scale(TOP + mCurrentSlashLength, height) 154 ); 155 156 mPath.reset(); 157 // Draw the slash vertically 158 mPath.addRoundRect(mSlashRect, radiusX, radiusY, Direction.CW); 159 // Rotate -45 + desired rotation 160 m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2); 161 mPath.transform(m); 162 canvas.drawPath(mPath, mPaint); 163 164 // Rotate back to vertical 165 m.setRotate(-mRotation - DEFAULT_ROTATION, width / 2, height / 2); 166 mPath.transform(m); 167 168 // Draw another rect right next to the first, for clipping 169 m.setTranslate(mSlashRect.width(), 0); 170 mPath.transform(m); 171 mPath.addRoundRect(mSlashRect, 1.0f * width, 1.0f * height, Direction.CW); 172 m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2); 173 mPath.transform(m); 174 canvas.clipOutPath(mPath); 175 176 mDrawable.draw(canvas); 177 canvas.restore(); 178 } 179 scale(float frac, int width)180 private float scale(float frac, int width) { 181 return frac * width; 182 } 183 updateRect(float left, float top, float right, float bottom)184 private void updateRect(float left, float top, float right, float bottom) { 185 mSlashRect.left = left; 186 mSlashRect.top = top; 187 mSlashRect.right = right; 188 mSlashRect.bottom = bottom; 189 } 190 191 @Override setTint(@olorInt int tintColor)192 public void setTint(@ColorInt int tintColor) { 193 super.setTint(tintColor); 194 mDrawable.setTint(tintColor); 195 mPaint.setColor(tintColor); 196 } 197 198 @Override setTintList(@ullable ColorStateList tint)199 public void setTintList(@Nullable ColorStateList tint) { 200 mTintList = tint; 201 super.setTintList(tint); 202 setDrawableTintList(tint); 203 mPaint.setColor(tint.getDefaultColor()); 204 invalidateSelf(); 205 } 206 setDrawableTintList(@ullable ColorStateList tint)207 protected void setDrawableTintList(@Nullable ColorStateList tint) { 208 mDrawable.setTintList(tint); 209 } 210 211 @Override setTintMode(@onNull Mode tintMode)212 public void setTintMode(@NonNull Mode tintMode) { 213 mTintMode = tintMode; 214 super.setTintMode(tintMode); 215 mDrawable.setTintMode(tintMode); 216 } 217 218 @Override setAlpha(@ntRangefrom = 0, to = 255) int alpha)219 public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { 220 mDrawable.setAlpha(alpha); 221 mPaint.setAlpha(alpha); 222 } 223 224 @Override setColorFilter(@ullable ColorFilter colorFilter)225 public void setColorFilter(@Nullable ColorFilter colorFilter) { 226 mDrawable.setColorFilter(colorFilter); 227 mPaint.setColorFilter(colorFilter); 228 } 229 230 @Override getOpacity()231 public int getOpacity() { 232 return 255; 233 } 234 } 235