• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.scrim;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.graphics.Canvas;
25 import android.graphics.ColorFilter;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.PixelFormat;
29 import android.graphics.Rect;
30 import android.graphics.Xfermode;
31 import android.graphics.drawable.Drawable;
32 import android.view.animation.DecelerateInterpolator;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.graphics.ColorUtils;
36 
37 /**
38  * Drawable used on SysUI scrims.
39  */
40 public class ScrimDrawable extends Drawable {
41     private static final String TAG = "ScrimDrawable";
42     private static final long COLOR_ANIMATION_DURATION = 2000;
43 
44     private final Paint mPaint;
45     private int mAlpha = 255;
46     private int mMainColor;
47     private ValueAnimator mColorAnimation;
48     private int mMainColorTo;
49     private float mCornerRadius;
50     private ConcaveInfo mConcaveInfo;
51     private int mBottomEdgePosition;
52     private boolean mCornerRadiusEnabled;
53 
ScrimDrawable()54     public ScrimDrawable() {
55         mPaint = new Paint();
56         mPaint.setStyle(Paint.Style.FILL);
57     }
58 
59     /**
60      * Sets the background color.
61      * @param mainColor the color.
62      * @param animated if transition should be interpolated.
63      */
setColor(int mainColor, boolean animated)64     public void setColor(int mainColor, boolean animated) {
65         if (mainColor == mMainColorTo) {
66             return;
67         }
68 
69         if (mColorAnimation != null && mColorAnimation.isRunning()) {
70             mColorAnimation.cancel();
71         }
72 
73         mMainColorTo = mainColor;
74 
75         if (animated) {
76             final int mainFrom = mMainColor;
77 
78             ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
79             anim.setDuration(COLOR_ANIMATION_DURATION);
80             anim.addUpdateListener(animation -> {
81                 float ratio = (float) animation.getAnimatedValue();
82                 mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio);
83                 invalidateSelf();
84             });
85             anim.addListener(new AnimatorListenerAdapter() {
86                 @Override
87                 public void onAnimationEnd(Animator animation, boolean isReverse) {
88                     if (mColorAnimation == animation) {
89                         mColorAnimation = null;
90                     }
91                 }
92             });
93             anim.setInterpolator(new DecelerateInterpolator());
94             anim.start();
95             mColorAnimation = anim;
96         } else {
97             mMainColor = mainColor;
98             invalidateSelf();
99         }
100     }
101 
102     @Override
setAlpha(int alpha)103     public void setAlpha(int alpha) {
104         if (alpha != mAlpha) {
105             mAlpha = alpha;
106             invalidateSelf();
107         }
108     }
109 
110     @Override
getAlpha()111     public int getAlpha() {
112         return mAlpha;
113     }
114 
115     @Override
setXfermode(@ullable Xfermode mode)116     public void setXfermode(@Nullable Xfermode mode) {
117         mPaint.setXfermode(mode);
118         invalidateSelf();
119     }
120 
121     @Override
setColorFilter(ColorFilter colorFilter)122     public void setColorFilter(ColorFilter colorFilter) {
123         mPaint.setColorFilter(colorFilter);
124     }
125 
126     @Override
getColorFilter()127     public ColorFilter getColorFilter() {
128         return mPaint.getColorFilter();
129     }
130 
131     @Override
getOpacity()132     public int getOpacity() {
133         return PixelFormat.TRANSLUCENT;
134     }
135 
136     /**
137      * Corner radius used by either concave or convex corners.
138      */
setRoundedCorners(float radius)139     public void setRoundedCorners(float radius) {
140         if (radius == mCornerRadius) {
141             return;
142         }
143         mCornerRadius = radius;
144         if (mConcaveInfo != null) {
145             mConcaveInfo.setCornerRadius(radius);
146             updatePath();
147         }
148         invalidateSelf();
149     }
150 
151     /**
152      * If we should draw a rounded rect instead of a rect.
153      */
setRoundedCornersEnabled(boolean enabled)154     public void setRoundedCornersEnabled(boolean enabled) {
155         if (mCornerRadiusEnabled == enabled) {
156             return;
157         }
158         mCornerRadiusEnabled = enabled;
159         invalidateSelf();
160     }
161 
162     /**
163      * If we should draw a concave rounded rect instead of a rect.
164      */
setBottomEdgeConcave(boolean enabled)165     public void setBottomEdgeConcave(boolean enabled) {
166         if (enabled && mConcaveInfo != null) {
167             return;
168         }
169         if (!enabled) {
170             mConcaveInfo = null;
171         } else {
172             mConcaveInfo = new ConcaveInfo();
173             mConcaveInfo.setCornerRadius(mCornerRadius);
174         }
175         invalidateSelf();
176     }
177 
178     /**
179      * Location of concave edge.
180      * @see #setBottomEdgeConcave(boolean)
181      */
setBottomEdgePosition(int y)182     public void setBottomEdgePosition(int y) {
183         if (mBottomEdgePosition == y) {
184             return;
185         }
186         mBottomEdgePosition = y;
187         if (mConcaveInfo == null) {
188             return;
189         }
190         updatePath();
191         invalidateSelf();
192     }
193 
194     @Override
draw(@onNull Canvas canvas)195     public void draw(@NonNull Canvas canvas) {
196         mPaint.setColor(mMainColor);
197         mPaint.setAlpha(mAlpha);
198         if (mConcaveInfo != null) {
199             drawConcave(canvas);
200         } else if (mCornerRadiusEnabled && mCornerRadius > 0) {
201             canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right,
202                     getBounds().bottom + mCornerRadius,
203                     /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint);
204         } else {
205             canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
206                     getBounds().bottom, mPaint);
207         }
208     }
209 
210     @Override
onBoundsChange(Rect bounds)211     protected void onBoundsChange(Rect bounds) {
212         updatePath();
213     }
214 
drawConcave(Canvas canvas)215     private void drawConcave(Canvas canvas) {
216         canvas.clipOutPath(mConcaveInfo.mPath);
217         canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
218                 mBottomEdgePosition + mConcaveInfo.mPathOverlap, mPaint);
219     }
220 
updatePath()221     private void updatePath() {
222         if (mConcaveInfo == null) {
223             return;
224         }
225         mConcaveInfo.mPath.reset();
226         float top = mBottomEdgePosition;
227         float bottom = mBottomEdgePosition + mConcaveInfo.mPathOverlap;
228         mConcaveInfo.mPath.addRoundRect(getBounds().left, top, getBounds().right, bottom,
229                 mConcaveInfo.mCornerRadii, Path.Direction.CW);
230     }
231 
232     @VisibleForTesting
getMainColor()233     public int getMainColor() {
234         return mMainColor;
235     }
236 
237     private static class ConcaveInfo {
238         private float mPathOverlap;
239         private final float[] mCornerRadii;
240         private final Path mPath = new Path();
241 
ConcaveInfo()242         ConcaveInfo() {
243             mCornerRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
244         }
245 
setCornerRadius(float radius)246         public void setCornerRadius(float radius) {
247             mPathOverlap = radius;
248             mCornerRadii[0] = radius;
249             mCornerRadii[1] = radius;
250             mCornerRadii[2] = radius;
251             mCornerRadii[3] = radius;
252         }
253     }
254 }
255