2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
16 package com
.android
.systemui
.recents
.views
;
18 import android
.content
.res
.Resources
;
19 import android
.graphics
.Canvas
;
20 import android
.graphics
.ColorFilter
;
21 import android
.graphics
.LinearGradient
;
22 import android
.graphics
.Paint
;
23 import android
.graphics
.Path
;
24 import android
.graphics
.PixelFormat
;
25 import android
.graphics
.RadialGradient
;
26 import android
.graphics
.Rect
;
27 import android
.graphics
.RectF
;
28 import android
.graphics
.Shader
;
29 import android
.graphics
.drawable
.Drawable
;
30 import android
.util
.Log
;
32 import com
.android
.systemui
.R
;
33 import com
.android
.systemui
.recents
.RecentsConfiguration
;
36 * A rounded rectangle drawable which also includes a shadow around. This is mostly copied from
37 * frameworks/support/v7/cardview/eclair-mr1/android/support/v7/widget/
38 * RoundRectDrawableWithShadow.java revision c42ba8c000d1e6ce85e152dfc17089a0a69e739f with a few
39 * modifications to suit our needs in SystemUI.
41 class FakeShadowDrawable
extends Drawable
{
42 // used to calculate content padding
43 final static double COS_45
= Math
.cos(Math
.toRadians(45));
45 final static float SHADOW_MULTIPLIER
= 1.5f
;
47 final float mInsetShadow
; // extra shadow to avoid gaps between card and shadow
49 Paint mCornerShadowPaint
;
51 Paint mEdgeShadowPaint
;
53 final RectF mCardBounds
;
57 Path mCornerShadowPath
;
59 // updated value with inset
62 // actual value set by developer
63 float mRawMaxShadowSize
;
65 // multiplied value to account for shadow offset
68 // actual value set by developer
71 private boolean mDirty
= true;
73 private final int mShadowStartColor
;
75 private final int mShadowEndColor
;
77 private boolean mAddPaddingForCorners
= true;
80 * If shadow size is set to a value above max shadow, we print a warning
82 private boolean mPrintedShadowClipWarning
= false;
84 public FakeShadowDrawable(Resources resources
, RecentsConfiguration config
) {
85 mShadowStartColor
= resources
.getColor(R
.color
.fake_shadow_start_color
);
86 mShadowEndColor
= resources
.getColor(R
.color
.fake_shadow_end_color
);
87 mInsetShadow
= resources
.getDimension(R
.dimen
.fake_shadow_inset
);
88 setShadowSize(resources
.getDimensionPixelSize(R
.dimen
.fake_shadow_size
),
89 resources
.getDimensionPixelSize(R
.dimen
.fake_shadow_size
));
90 mCornerShadowPaint
= new Paint(Paint
.ANTI_ALIAS_FLAG
| Paint
.DITHER_FLAG
);
91 mCornerShadowPaint
.setStyle(Paint
.Style
.FILL
);
92 mCornerShadowPaint
.setDither(true);
93 mCornerRadius
= resources
.getDimensionPixelSize(
94 R
.dimen
.recents_task_view_rounded_corners_radius
);
95 mCardBounds
= new RectF();
96 mEdgeShadowPaint
= new Paint(mCornerShadowPaint
);
100 public void setAlpha(int alpha
) {
101 mCornerShadowPaint
.setAlpha(alpha
);
102 mEdgeShadowPaint
.setAlpha(alpha
);
106 protected void onBoundsChange(Rect bounds
) {
107 super.onBoundsChange(bounds
);
111 void setShadowSize(float shadowSize
, float maxShadowSize
) {
112 if (shadowSize
< 0 || maxShadowSize
< 0) {
113 throw new IllegalArgumentException("invalid shadow size");
115 if (shadowSize
> maxShadowSize
) {
116 shadowSize
= maxShadowSize
;
117 if (!mPrintedShadowClipWarning
) {
118 Log
.w("CardView", "Shadow size is being clipped by the max shadow size. See "
119 + "{CardView#setMaxCardElevation}.");
120 mPrintedShadowClipWarning
= true;
123 if (mRawShadowSize
== shadowSize
&& mRawMaxShadowSize
== maxShadowSize
) {
126 mRawShadowSize
= shadowSize
;
127 mRawMaxShadowSize
= maxShadowSize
;
128 mShadowSize
= shadowSize
* SHADOW_MULTIPLIER
+ mInsetShadow
;
129 mMaxShadowSize
= maxShadowSize
+ mInsetShadow
;
135 public boolean getPadding(Rect padding
) {
136 int vOffset
= (int) Math
.ceil(calculateVerticalPadding(mRawMaxShadowSize
, mCornerRadius
,
137 mAddPaddingForCorners
));
138 int hOffset
= (int) Math
.ceil(calculateHorizontalPadding(mRawMaxShadowSize
, mCornerRadius
,
139 mAddPaddingForCorners
));
140 padding
.set(hOffset
, vOffset
, hOffset
, vOffset
);
144 static float calculateVerticalPadding(float maxShadowSize
, float cornerRadius
,
145 boolean addPaddingForCorners
) {
146 if (addPaddingForCorners
) {
147 return (float) (maxShadowSize
* SHADOW_MULTIPLIER
+ (1 - COS_45
) * cornerRadius
);
149 return maxShadowSize
* SHADOW_MULTIPLIER
;
153 static float calculateHorizontalPadding(float maxShadowSize
, float cornerRadius
,
154 boolean addPaddingForCorners
) {
155 if (addPaddingForCorners
) {
156 return (float) (maxShadowSize
+ (1 - COS_45
) * cornerRadius
);
158 return maxShadowSize
;
163 public void setColorFilter(ColorFilter colorFilter
) {
164 mCornerShadowPaint
.setColorFilter(colorFilter
);
165 mEdgeShadowPaint
.setColorFilter(colorFilter
);
169 public int getOpacity() {
170 return PixelFormat
.OPAQUE
;
174 public void draw(Canvas canvas
) {
176 buildComponents(getBounds());
179 canvas
.translate(0, mRawShadowSize
/ 4);
181 canvas
.translate(0, -mRawShadowSize
/ 4);
184 private void drawShadow(Canvas canvas
) {
185 final float edgeShadowTop
= -mCornerRadius
- mShadowSize
;
186 final float inset
= mCornerRadius
+ mInsetShadow
+ mRawShadowSize
/ 2;
187 final boolean drawHorizontalEdges
= mCardBounds
.width() - 2 * inset
> 0;
188 final boolean drawVerticalEdges
= mCardBounds
.height() - 2 * inset
> 0;
190 int saved
= canvas
.save();
191 canvas
.translate(mCardBounds
.left
+ inset
, mCardBounds
.top
+ inset
);
192 canvas
.drawPath(mCornerShadowPath
, mCornerShadowPaint
);
193 if (drawHorizontalEdges
) {
194 canvas
.drawRect(0, edgeShadowTop
,
195 mCardBounds
.width() - 2 * inset
, -mCornerRadius
,
198 canvas
.restoreToCount(saved
);
200 saved
= canvas
.save();
201 canvas
.translate(mCardBounds
.right
- inset
, mCardBounds
.bottom
- inset
);
203 canvas
.drawPath(mCornerShadowPath
, mCornerShadowPaint
);
204 if (drawHorizontalEdges
) {
205 canvas
.drawRect(0, edgeShadowTop
,
206 mCardBounds
.width() - 2 * inset
, -mCornerRadius
+ mShadowSize
,
209 canvas
.restoreToCount(saved
);
211 saved
= canvas
.save();
212 canvas
.translate(mCardBounds
.left
+ inset
, mCardBounds
.bottom
- inset
);
214 canvas
.drawPath(mCornerShadowPath
, mCornerShadowPaint
);
215 if (drawVerticalEdges
) {
216 canvas
.drawRect(0, edgeShadowTop
,
217 mCardBounds
.height() - 2 * inset
, -mCornerRadius
, mEdgeShadowPaint
);
219 canvas
.restoreToCount(saved
);
221 saved
= canvas
.save();
222 canvas
.translate(mCardBounds
.right
- inset
, mCardBounds
.top
+ inset
);
224 canvas
.drawPath(mCornerShadowPath
, mCornerShadowPaint
);
225 if (drawVerticalEdges
) {
226 canvas
.drawRect(0, edgeShadowTop
,
227 mCardBounds
.height() - 2 * inset
, -mCornerRadius
, mEdgeShadowPaint
);
229 canvas
.restoreToCount(saved
);
232 private void buildShadowCorners() {
233 RectF innerBounds
= new RectF(-mCornerRadius
, -mCornerRadius
, mCornerRadius
, mCornerRadius
);
234 RectF outerBounds
= new RectF(innerBounds
);
235 outerBounds
.inset(-mShadowSize
, -mShadowSize
);
237 if (mCornerShadowPath
== null) {
238 mCornerShadowPath
= new Path();
240 mCornerShadowPath
.reset();
242 mCornerShadowPath
.setFillType(Path
.FillType
.EVEN_ODD
);
243 mCornerShadowPath
.moveTo(-mCornerRadius
, 0);
244 mCornerShadowPath
.rLineTo(-mShadowSize
, 0);
246 mCornerShadowPath
.arcTo(outerBounds
, 180f
, 90f
, false);
248 mCornerShadowPath
.arcTo(innerBounds
, 270f
, -90f
, false);
249 mCornerShadowPath
.close();
251 float startRatio
= mCornerRadius
/ (mCornerRadius
+ mShadowSize
);
252 mCornerShadowPaint
.setShader(new RadialGradient(0, 0, mCornerRadius
+ mShadowSize
,
253 new int[]{mShadowStartColor
, mShadowStartColor
, mShadowEndColor
},
254 new float[]{0f
, startRatio
, 1f
}
255 , Shader
.TileMode
.CLAMP
));
257 // we offset the content shadowSize/2 pixels up to make it more realistic.
258 // this is why edge shadow shader has some extra space
259 // When drawing bottom edge shadow, we use that extra space.
260 mEdgeShadowPaint
.setShader(new LinearGradient(0, -mCornerRadius
+ mShadowSize
, 0,
261 -mCornerRadius
- mShadowSize
,
262 new int[]{mShadowStartColor
, mShadowStartColor
, mShadowEndColor
},
263 new float[]{0f
, .5f
, 1f
}, Shader
.TileMode
.CLAMP
));
266 private void buildComponents(Rect bounds
) {
267 // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
268 // We could have different top-bottom offsets to avoid extra gap above but in that case
269 // center aligning Views inside the CardView would be problematic.
270 final float verticalOffset
= mMaxShadowSize
* SHADOW_MULTIPLIER
;
271 mCardBounds
.set(bounds
.left
+ mMaxShadowSize
, bounds
.top
+ verticalOffset
,
272 bounds
.right
- mMaxShadowSize
, bounds
.bottom
- verticalOffset
);
273 buildShadowCorners();
276 float getMinWidth() {
277 final float content
= 2 *
278 Math
.max(mRawMaxShadowSize
, mCornerRadius
+ mInsetShadow
+ mRawMaxShadowSize
/ 2);
279 return content
+ (mRawMaxShadowSize
+ mInsetShadow
) * 2;
282 float getMinHeight() {
283 final float content
= 2 * Math
.max(mRawMaxShadowSize
, mCornerRadius
+ mInsetShadow
284 + mRawMaxShadowSize
* SHADOW_MULTIPLIER
/ 2);
285 return content
+ (mRawMaxShadowSize
* SHADOW_MULTIPLIER
+ mInsetShadow
) * 2;