Source code
package android.support.design.widget;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Path.FillType;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.Drawable;
import android.support.design.R;
import android.support.v7.graphics.drawable.DrawableWrapper;
class ShadowDrawableWrapper extends DrawableWrapper {
static final double COS_45 = Math.cos(Math.toRadians(45.0d));
static final float SHADOW_BOTTOM_SCALE = 1.0f;
static final float SHADOW_HORIZ_SCALE = 0.5f;
static final float SHADOW_MULTIPLIER = 1.5f;
static final float SHADOW_TOP_SCALE = 0.25f;
private boolean mAddPaddingForCorners = true;
final RectF mContentBounds;
float mCornerRadius;
final Paint mCornerShadowPaint;
Path mCornerShadowPath;
private boolean mDirty = true;
final Paint mEdgeShadowPaint;
float mMaxShadowSize;
private boolean mPrintedShadowClipWarning = false;
float mRawMaxShadowSize;
float mRawShadowSize;
private final int mShadowEndColor;
private final int mShadowMiddleColor;
float mShadowSize;
private final int mShadowStartColor;
public ShadowDrawableWrapper(Resources resources, Drawable content, float radius, float shadowSize, float maxShadowSize) {
super(content);
this.mShadowStartColor = resources.getColor(R.color.design_fab_shadow_start_color);
this.mShadowMiddleColor = resources.getColor(R.color.design_fab_shadow_mid_color);
this.mShadowEndColor = resources.getColor(R.color.design_fab_shadow_end_color);
this.mCornerShadowPaint = new Paint(5);
this.mCornerShadowPaint.setStyle(Style.FILL);
this.mCornerRadius = (float) Math.round(radius);
this.mContentBounds = new RectF();
this.mEdgeShadowPaint = new Paint(this.mCornerShadowPaint);
this.mEdgeShadowPaint.setAntiAlias(false);
setShadowSize(shadowSize, maxShadowSize);
}
private static int toEven(float value) {
int i = Math.round(value);
return i % 2 == 1 ? i - 1 : i;
}
public void setAddPaddingForCorners(boolean addPaddingForCorners) {
this.mAddPaddingForCorners = addPaddingForCorners;
invalidateSelf();
}
public void setAlpha(int alpha) {
super.setAlpha(alpha);
this.mCornerShadowPaint.setAlpha(alpha);
this.mEdgeShadowPaint.setAlpha(alpha);
}
protected void onBoundsChange(Rect bounds) {
this.mDirty = true;
}
void setShadowSize(float shadowSize, float maxShadowSize) {
if (shadowSize < 0.0f || maxShadowSize < 0.0f) {
throw new IllegalArgumentException("invalid shadow size");
}
shadowSize = (float) toEven(shadowSize);
maxShadowSize = (float) toEven(maxShadowSize);
if (shadowSize > maxShadowSize) {
shadowSize = maxShadowSize;
if (!this.mPrintedShadowClipWarning) {
this.mPrintedShadowClipWarning = true;
}
}
if (this.mRawShadowSize != shadowSize || this.mRawMaxShadowSize != maxShadowSize) {
this.mRawShadowSize = shadowSize;
this.mRawMaxShadowSize = maxShadowSize;
this.mShadowSize = (float) Math.round(SHADOW_MULTIPLIER * shadowSize);
this.mMaxShadowSize = maxShadowSize;
this.mDirty = true;
invalidateSelf();
}
}
public boolean getPadding(Rect padding) {
int vOffset = (int) Math.ceil((double) calculateVerticalPadding(this.mRawMaxShadowSize, this.mCornerRadius, this.mAddPaddingForCorners));
int hOffset = (int) Math.ceil((double) calculateHorizontalPadding(this.mRawMaxShadowSize, this.mCornerRadius, this.mAddPaddingForCorners));
padding.set(hOffset, vOffset, hOffset, vOffset);
return true;
}
public static float calculateVerticalPadding(float maxShadowSize, float cornerRadius, boolean addPaddingForCorners) {
if (addPaddingForCorners) {
return (float) (((double) (SHADOW_MULTIPLIER * maxShadowSize)) + ((1.0d - COS_45) * ((double) cornerRadius)));
}
return SHADOW_MULTIPLIER * maxShadowSize;
}
public static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius, boolean addPaddingForCorners) {
if (addPaddingForCorners) {
return (float) (((double) maxShadowSize) + ((1.0d - COS_45) * ((double) cornerRadius)));
}
return maxShadowSize;
}
public int getOpacity() {
return -3;
}
public void setCornerRadius(float radius) {
radius = (float) Math.round(radius);
if (this.mCornerRadius != radius) {
this.mCornerRadius = radius;
this.mDirty = true;
invalidateSelf();
}
}
public void draw(Canvas canvas) {
if (this.mDirty) {
buildComponents(getBounds());
this.mDirty = false;
}
drawShadow(canvas);
super.draw(canvas);
}
private void drawShadow(Canvas canvas) {
float edgeShadowTop = (-this.mCornerRadius) - this.mShadowSize;
float shadowOffset = this.mCornerRadius;
boolean drawHorizontalEdges = this.mContentBounds.width() - (2.0f * shadowOffset) > 0.0f;
boolean drawVerticalEdges = this.mContentBounds.height() - (2.0f * shadowOffset) > 0.0f;
float shadowScaleHorizontal = shadowOffset / (shadowOffset + (this.mRawShadowSize - (this.mRawShadowSize * SHADOW_HORIZ_SCALE)));
float shadowScaleTop = shadowOffset / (shadowOffset + (this.mRawShadowSize - (this.mRawShadowSize * SHADOW_TOP_SCALE)));
float shadowScaleBottom = shadowOffset / (shadowOffset + (this.mRawShadowSize - (this.mRawShadowSize * SHADOW_BOTTOM_SCALE)));
int saved = canvas.save();
canvas.translate(this.mContentBounds.left + shadowOffset, this.mContentBounds.top + shadowOffset);
canvas.scale(shadowScaleHorizontal, shadowScaleTop);
canvas.drawPath(this.mCornerShadowPath, this.mCornerShadowPaint);
if (drawHorizontalEdges) {
canvas.scale(SHADOW_BOTTOM_SCALE / shadowScaleHorizontal, SHADOW_BOTTOM_SCALE);
canvas.drawRect(0.0f, edgeShadowTop, this.mContentBounds.width() - (2.0f * shadowOffset), -this.mCornerRadius, this.mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
saved = canvas.save();
canvas.translate(this.mContentBounds.right - shadowOffset, this.mContentBounds.bottom - shadowOffset);
canvas.scale(shadowScaleHorizontal, shadowScaleBottom);
canvas.rotate(180.0f);
canvas.drawPath(this.mCornerShadowPath, this.mCornerShadowPaint);
if (drawHorizontalEdges) {
canvas.scale(SHADOW_BOTTOM_SCALE / shadowScaleHorizontal, SHADOW_BOTTOM_SCALE);
canvas.drawRect(0.0f, edgeShadowTop, this.mContentBounds.width() - (2.0f * shadowOffset), this.mShadowSize + (-this.mCornerRadius), this.mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
saved = canvas.save();
canvas.translate(this.mContentBounds.left + shadowOffset, this.mContentBounds.bottom - shadowOffset);
canvas.scale(shadowScaleHorizontal, shadowScaleBottom);
canvas.rotate(270.0f);
canvas.drawPath(this.mCornerShadowPath, this.mCornerShadowPaint);
if (drawVerticalEdges) {
canvas.scale(SHADOW_BOTTOM_SCALE / shadowScaleBottom, SHADOW_BOTTOM_SCALE);
canvas.drawRect(0.0f, edgeShadowTop, this.mContentBounds.height() - (2.0f * shadowOffset), -this.mCornerRadius, this.mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
saved = canvas.save();
canvas.translate(this.mContentBounds.right - shadowOffset, this.mContentBounds.top + shadowOffset);
canvas.scale(shadowScaleHorizontal, shadowScaleTop);
canvas.rotate(90.0f);
canvas.drawPath(this.mCornerShadowPath, this.mCornerShadowPaint);
if (drawVerticalEdges) {
canvas.scale(SHADOW_BOTTOM_SCALE / shadowScaleTop, SHADOW_BOTTOM_SCALE);
canvas.drawRect(0.0f, edgeShadowTop, this.mContentBounds.height() - (2.0f * shadowOffset), -this.mCornerRadius, this.mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
}
private void buildShadowCorners() {
RectF innerBounds = new RectF(-this.mCornerRadius, -this.mCornerRadius, this.mCornerRadius, this.mCornerRadius);
RectF outerBounds = new RectF(innerBounds);
outerBounds.inset(-this.mShadowSize, -this.mShadowSize);
if (this.mCornerShadowPath == null) {
this.mCornerShadowPath = new Path();
} else {
this.mCornerShadowPath.reset();
}
this.mCornerShadowPath.setFillType(FillType.EVEN_ODD);
this.mCornerShadowPath.moveTo(-this.mCornerRadius, 0.0f);
this.mCornerShadowPath.rLineTo(-this.mShadowSize, 0.0f);
this.mCornerShadowPath.arcTo(outerBounds, 180.0f, 90.0f, false);
this.mCornerShadowPath.arcTo(innerBounds, 270.0f, -90.0f, false);
this.mCornerShadowPath.close();
float shadowRadius = -outerBounds.top;
if (shadowRadius > 0.0f) {
float startRatio = this.mCornerRadius / shadowRadius;
float midRatio = startRatio + ((SHADOW_BOTTOM_SCALE - startRatio) / 2.0f);
this.mCornerShadowPaint.setShader(new RadialGradient(0.0f, 0.0f, shadowRadius, new int[]{0, this.mShadowStartColor, this.mShadowMiddleColor, this.mShadowEndColor}, new float[]{0.0f, startRatio, midRatio, SHADOW_BOTTOM_SCALE}, TileMode.CLAMP));
}
this.mEdgeShadowPaint.setShader(new LinearGradient(0.0f, innerBounds.top, 0.0f, outerBounds.top, new int[]{this.mShadowStartColor, this.mShadowMiddleColor, this.mShadowEndColor}, new float[]{0.0f, SHADOW_HORIZ_SCALE, SHADOW_BOTTOM_SCALE}, TileMode.CLAMP));
this.mEdgeShadowPaint.setAntiAlias(false);
}
private void buildComponents(Rect bounds) {
float verticalOffset = this.mRawMaxShadowSize * SHADOW_MULTIPLIER;
this.mContentBounds.set(((float) bounds.left) + this.mRawMaxShadowSize, ((float) bounds.top) + verticalOffset, ((float) bounds.right) - this.mRawMaxShadowSize, ((float) bounds.bottom) - verticalOffset);
getWrappedDrawable().setBounds((int) this.mContentBounds.left, (int) this.mContentBounds.top, (int) this.mContentBounds.right, (int) this.mContentBounds.bottom);
buildShadowCorners();
}
public float getCornerRadius() {
return this.mCornerRadius;
}
public void setShadowSize(float size) {
setShadowSize(size, this.mRawMaxShadowSize);
}
public void setMaxShadowSize(float size) {
setShadowSize(this.mRawShadowSize, size);
}
public float getShadowSize() {
return this.mRawShadowSize;
}
public float getMaxShadowSize() {
return this.mRawMaxShadowSize;
}
public float getMinWidth() {
return (this.mRawMaxShadowSize * 2.0f) + (2.0f * Math.max(this.mRawMaxShadowSize, this.mCornerRadius + (this.mRawMaxShadowSize / 2.0f)));
}
public float getMinHeight() {
return ((this.mRawMaxShadowSize * SHADOW_MULTIPLIER) * 2.0f) + (2.0f * Math.max(this.mRawMaxShadowSize, this.mCornerRadius + ((this.mRawMaxShadowSize * SHADOW_MULTIPLIER) / 2.0f)));
}
}