Move to Android N-MR1 SDK.
[android_tools.git] / sdk / sources / android-25 / com / android / systemui / recents / views / FakeShadowDrawable.java
blobd64a67600513466c33fbc0e285ceb55603d0f00b
1 /*
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;
35 /**
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;
55 float mCornerRadius;
57 Path mCornerShadowPath;
59 // updated value with inset
60 float mMaxShadowSize;
62 // actual value set by developer
63 float mRawMaxShadowSize;
65 // multiplied value to account for shadow offset
66 float mShadowSize;
68 // actual value set by developer
69 float mRawShadowSize;
71 private boolean mDirty = true;
73 private final int mShadowStartColor;
75 private final int mShadowEndColor;
77 private boolean mAddPaddingForCorners = true;
79 /**
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);
99 @Override
100 public void setAlpha(int alpha) {
101 mCornerShadowPaint.setAlpha(alpha);
102 mEdgeShadowPaint.setAlpha(alpha);
105 @Override
106 protected void onBoundsChange(Rect bounds) {
107 super.onBoundsChange(bounds);
108 mDirty = true;
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) {
124 return;
126 mRawShadowSize = shadowSize;
127 mRawMaxShadowSize = maxShadowSize;
128 mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
129 mMaxShadowSize = maxShadowSize + mInsetShadow;
130 mDirty = true;
131 invalidateSelf();
134 @Override
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);
141 return true;
144 static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
145 boolean addPaddingForCorners) {
146 if (addPaddingForCorners) {
147 return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
148 } else {
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);
157 } else {
158 return maxShadowSize;
162 @Override
163 public void setColorFilter(ColorFilter colorFilter) {
164 mCornerShadowPaint.setColorFilter(colorFilter);
165 mEdgeShadowPaint.setColorFilter(colorFilter);
168 @Override
169 public int getOpacity() {
170 return PixelFormat.OPAQUE;
173 @Override
174 public void draw(Canvas canvas) {
175 if (mDirty) {
176 buildComponents(getBounds());
177 mDirty = false;
179 canvas.translate(0, mRawShadowSize / 4);
180 drawShadow(canvas);
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;
189 // LT
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,
196 mEdgeShadowPaint);
198 canvas.restoreToCount(saved);
199 // RB
200 saved = canvas.save();
201 canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
202 canvas.rotate(180f);
203 canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
204 if (drawHorizontalEdges) {
205 canvas.drawRect(0, edgeShadowTop,
206 mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
207 mEdgeShadowPaint);
209 canvas.restoreToCount(saved);
210 // LB
211 saved = canvas.save();
212 canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
213 canvas.rotate(270f);
214 canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
215 if (drawVerticalEdges) {
216 canvas.drawRect(0, edgeShadowTop,
217 mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
219 canvas.restoreToCount(saved);
220 // RT
221 saved = canvas.save();
222 canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
223 canvas.rotate(90f);
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();
239 } else {
240 mCornerShadowPath.reset();
242 mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
243 mCornerShadowPath.moveTo(-mCornerRadius, 0);
244 mCornerShadowPath.rLineTo(-mShadowSize, 0);
245 // outer arc
246 mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
247 // inner arc
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;