偷用FloatActionButton的源码实现可更改颜色的阴影效果

FloatActionButton用来做阴影的主要是ShadowDrawableWrapper,这个功能相对独立,可以直接把这个类和他继承的DrawableWrapper 拷出来放在自己的包下面,然后重新导包就行了。


image.png

这个图上面的三个颜色,我们可以自定义,也就是阴影的渐变三色。
源码:
public class ShadowDrawableWrapper extends DrawableWrapper {

static final double COS_45 = Math.cos(Math.toRadians(45.0D));
static final float SHADOW_MULTIPLIER = 1.5F;
static final float SHADOW_TOP_SCALE = 0.25F;
static final float SHADOW_HORIZ_SCALE = 0.5F;
static final float SHADOW_BOTTOM_SCALE = 1.0F;
final Paint cornerShadowPaint;
final Paint edgeShadowPaint;
final RectF contentBounds;
float cornerRadius;
Path cornerShadowPath;
float maxShadowSize;
float rawMaxShadowSize;
float shadowSize;
float rawShadowSize;
private boolean dirty = true;
private final int shadowStartColor;
private final int shadowMiddleColor;
private final int shadowEndColor;
private boolean addPaddingForCorners = true;
private float rotation;
private boolean printedShadowClipWarning = false;

public ShadowDrawableWrapper(Context context, Drawable content, float radius, float shadowSize, float maxShadowSize) {
    super(content);
    this.shadowStartColor = ContextCompat.getColor(context, R.color.design_fab_shadow_start_color);
    this.shadowMiddleColor = ContextCompat.getColor(context, R.color.design_fab_shadow_mid_color);
    this.shadowEndColor = ContextCompat.getColor(context, R.color.design_fab_shadow_end_color);
    this.cornerShadowPaint = new Paint(5);
    this.cornerShadowPaint.setStyle(Paint.Style.FILL);
    this.cornerRadius = (float)Math.round(radius);
    this.contentBounds = new RectF();
    this.edgeShadowPaint = new Paint(this.cornerShadowPaint);
    this.edgeShadowPaint.setAntiAlias(false);
    this.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.addPaddingForCorners = addPaddingForCorners;
    this.invalidateSelf();
}

public void setAlpha(int alpha) {
    super.setAlpha(alpha);
    this.cornerShadowPaint.setAlpha(alpha);
    this.edgeShadowPaint.setAlpha(alpha);
}

protected void onBoundsChange(Rect bounds) {
    this.dirty = true;
}

public void setShadowSize(float shadowSize, float maxShadowSize) {
    if (shadowSize >= 0.0F && maxShadowSize >= 0.0F) {
        shadowSize = (float)toEven(shadowSize);
        maxShadowSize = (float)toEven(maxShadowSize);
        if (shadowSize > maxShadowSize) {
            shadowSize = maxShadowSize;
            if (!this.printedShadowClipWarning) {
                this.printedShadowClipWarning = true;
            }
        }

        if (this.rawShadowSize != shadowSize || this.rawMaxShadowSize != maxShadowSize) {
            this.rawShadowSize = shadowSize;
            this.rawMaxShadowSize = maxShadowSize;
            this.shadowSize = (float)Math.round(shadowSize * 1.5F);
            this.maxShadowSize = maxShadowSize;
            this.dirty = true;
            this.invalidateSelf();
        }
    } else {
        throw new IllegalArgumentException("invalid shadow size");
    }
}

public void setShadowSize(float size) {
    this.setShadowSize(size, this.rawMaxShadowSize);
}

public float getShadowSize() {
    return this.rawShadowSize;
}

public boolean getPadding(Rect padding) {
    int vOffset = (int)Math.ceil((double)calculateVerticalPadding(this.rawMaxShadowSize, this.cornerRadius, this.addPaddingForCorners));
    int hOffset = (int)Math.ceil((double)calculateHorizontalPadding(this.rawMaxShadowSize, this.cornerRadius, this.addPaddingForCorners));
    padding.set(hOffset, vOffset, hOffset, vOffset);
    return true;
}

public static float calculateVerticalPadding(float maxShadowSize, float cornerRadius, boolean addPaddingForCorners) {
    return addPaddingForCorners ? (float)((double)(maxShadowSize * 1.5F) + (1.0D - COS_45) * (double)cornerRadius) : maxShadowSize * 1.5F;
}

public static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius, boolean addPaddingForCorners) {
    return addPaddingForCorners ? (float)((double)maxShadowSize + (1.0D - COS_45) * (double)cornerRadius) : maxShadowSize;
}

public int getOpacity() {
    return -3;
}

public void setCornerRadius(float radius) {
    radius = (float)Math.round(radius);
    if (this.cornerRadius != radius) {
        this.cornerRadius = radius;
        this.dirty = true;
        this.invalidateSelf();
    }
}

public void draw(Canvas canvas) {
    if (this.dirty) {
        this.buildComponents(this.getBounds());
        this.dirty = false;
    }

    this.drawShadow(canvas);
    super.draw(canvas);
}

public final void setRotation(float rotation) {
    if (this.rotation != rotation) {
        this.rotation = rotation;
        this.invalidateSelf();
    }

}

private void drawShadow(Canvas canvas) {
    int rotateSaved = canvas.save();
    canvas.rotate(this.rotation, this.contentBounds.centerX(), this.contentBounds.centerY());
    float edgeShadowTop = -this.cornerRadius - this.shadowSize;
    float shadowOffset = this.cornerRadius;
    boolean drawHorizontalEdges = this.contentBounds.width() - 2.0F * shadowOffset > 0.0F;
    boolean drawVerticalEdges = this.contentBounds.height() - 2.0F * shadowOffset > 0.0F;
    float shadowOffsetTop = this.rawShadowSize - this.rawShadowSize * 0.25F;
    float shadowOffsetHorizontal = this.rawShadowSize - this.rawShadowSize * 0.5F;
    float shadowOffsetBottom = this.rawShadowSize - this.rawShadowSize * 1.0F;
    float shadowScaleHorizontal = shadowOffset / (shadowOffset + shadowOffsetHorizontal);
    float shadowScaleTop = shadowOffset / (shadowOffset + shadowOffsetTop);
    float shadowScaleBottom = shadowOffset / (shadowOffset + shadowOffsetBottom);
    int saved = canvas.save();
    canvas.translate(this.contentBounds.left + shadowOffset, this.contentBounds.top + shadowOffset);
    canvas.scale(shadowScaleHorizontal, shadowScaleTop);
    canvas.drawPath(this.cornerShadowPath, this.cornerShadowPaint);
    if (drawHorizontalEdges) {
        canvas.scale(1.0F / shadowScaleHorizontal, 1.0F);
        canvas.drawRect(0.0F, edgeShadowTop, this.contentBounds.width() - 2.0F * shadowOffset, -this.cornerRadius, this.edgeShadowPaint);
    }

    canvas.restoreToCount(saved);
    saved = canvas.save();
    canvas.translate(this.contentBounds.right - shadowOffset, this.contentBounds.bottom - shadowOffset);
    canvas.scale(shadowScaleHorizontal, shadowScaleBottom);
    canvas.rotate(180.0F);
    canvas.drawPath(this.cornerShadowPath, this.cornerShadowPaint);
    if (drawHorizontalEdges) {
        canvas.scale(1.0F / shadowScaleHorizontal, 1.0F);
        canvas.drawRect(0.0F, edgeShadowTop, this.contentBounds.width() - 2.0F * shadowOffset, -this.cornerRadius + this.shadowSize, this.edgeShadowPaint);
    }

    canvas.restoreToCount(saved);
    saved = canvas.save();
    canvas.translate(this.contentBounds.left + shadowOffset, this.contentBounds.bottom - shadowOffset);
    canvas.scale(shadowScaleHorizontal, shadowScaleBottom);
    canvas.rotate(270.0F);
    canvas.drawPath(this.cornerShadowPath, this.cornerShadowPaint);
    if (drawVerticalEdges) {
        canvas.scale(1.0F / shadowScaleBottom, 1.0F);
        canvas.drawRect(0.0F, edgeShadowTop, this.contentBounds.height() - 2.0F * shadowOffset, -this.cornerRadius, this.edgeShadowPaint);
    }

    canvas.restoreToCount(saved);
    saved = canvas.save();
    canvas.translate(this.contentBounds.right - shadowOffset, this.contentBounds.top + shadowOffset);
    canvas.scale(shadowScaleHorizontal, shadowScaleTop);
    canvas.rotate(90.0F);
    canvas.drawPath(this.cornerShadowPath, this.cornerShadowPaint);
    if (drawVerticalEdges) {
        canvas.scale(1.0F / shadowScaleTop, 1.0F);
        canvas.drawRect(0.0F, edgeShadowTop, this.contentBounds.height() - 2.0F * shadowOffset, -this.cornerRadius, this.edgeShadowPaint);
    }

    canvas.restoreToCount(saved);
    canvas.restoreToCount(rotateSaved);
}

private void buildShadowCorners() {
    RectF innerBounds = new RectF(-this.cornerRadius, -this.cornerRadius, this.cornerRadius, this.cornerRadius);
    RectF outerBounds = new RectF(innerBounds);
    outerBounds.inset(-this.shadowSize, -this.shadowSize);
    if (this.cornerShadowPath == null) {
        this.cornerShadowPath = new Path();
    } else {
        this.cornerShadowPath.reset();
    }

    this.cornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
    this.cornerShadowPath.moveTo(-this.cornerRadius, 0.0F);
    this.cornerShadowPath.rLineTo(-this.shadowSize, 0.0F);
    this.cornerShadowPath.arcTo(outerBounds, 180.0F, 90.0F, false);
    this.cornerShadowPath.arcTo(innerBounds, 270.0F, -90.0F, false);
    this.cornerShadowPath.close();
    float shadowRadius = -outerBounds.top;
    if (shadowRadius > 0.0F) {
        float startRatio = this.cornerRadius / shadowRadius;
        float midRatio = startRatio + (1.0F - startRatio) / 2.0F;
        this.cornerShadowPaint.setShader(new RadialGradient(0.0F, 0.0F, shadowRadius, new int[]{0, this.shadowStartColor, this.shadowMiddleColor, this.shadowEndColor}, new float[]{0.0F, startRatio, midRatio, 1.0F}, Shader.TileMode.CLAMP));
    }

    this.edgeShadowPaint.setShader(new LinearGradient(0.0F, innerBounds.top, 0.0F, outerBounds.top, new int[]{this.shadowStartColor, this.shadowMiddleColor, this.shadowEndColor}, new float[]{0.0F, 0.5F, 1.0F}, Shader.TileMode.CLAMP));
    this.edgeShadowPaint.setAntiAlias(false);
}

private void buildComponents(Rect bounds) {
    float verticalOffset = this.rawMaxShadowSize * 1.5F;
    this.contentBounds.set((float)bounds.left + this.rawMaxShadowSize, (float)bounds.top + verticalOffset, (float)bounds.right - this.rawMaxShadowSize, (float)bounds.bottom - verticalOffset);
    this.getWrappedDrawable().setBounds((int)this.contentBounds.left, (int)this.contentBounds.top, (int)this.contentBounds.right, (int)this.contentBounds.bottom);
    this.buildShadowCorners();
}

public float getCornerRadius() {
    return this.cornerRadius;
}

public void setMaxShadowSize(float size) {
    this.setShadowSize(this.rawShadowSize, size);
}

public float getMaxShadowSize() {
    return this.rawMaxShadowSize;
}

public float getMinWidth() {
    float content = 2.0F * Math.max(this.rawMaxShadowSize, this.cornerRadius + this.rawMaxShadowSize / 2.0F);
    return content + this.rawMaxShadowSize * 2.0F;
}

public float getMinHeight() {
    float content = 2.0F * Math.max(this.rawMaxShadowSize, this.cornerRadius + this.rawMaxShadowSize * 1.5F / 2.0F);
    return content + this.rawMaxShadowSize * 1.5F * 2.0F;
}

}

DrawableWrapper源码:

@RestrictTo({RestrictTo.Scope.LIBRARY_GROUP})
public class DrawableWrapper extends Drawable implements Drawable.Callback {
private Drawable mDrawable;

public DrawableWrapper(Drawable drawable) {
    this.setWrappedDrawable(drawable);
}

public void draw(Canvas canvas) {
    this.mDrawable.draw(canvas);
}

protected void onBoundsChange(Rect bounds) {
    this.mDrawable.setBounds(bounds);
}

public void setChangingConfigurations(int configs) {
    this.mDrawable.setChangingConfigurations(configs);
}

public int getChangingConfigurations() {
    return this.mDrawable.getChangingConfigurations();
}

public void setDither(boolean dither) {
    this.mDrawable.setDither(dither);
}

public void setFilterBitmap(boolean filter) {
    this.mDrawable.setFilterBitmap(filter);
}

public void setAlpha(int alpha) {
    this.mDrawable.setAlpha(alpha);
}

public void setColorFilter(ColorFilter cf) {
    this.mDrawable.setColorFilter(cf);
}

public boolean isStateful() {
    return this.mDrawable.isStateful();
}

public boolean setState(int[] stateSet) {
    return this.mDrawable.setState(stateSet);
}

public int[] getState() {
    return this.mDrawable.getState();
}

public void jumpToCurrentState() {
    DrawableCompat.jumpToCurrentState(this.mDrawable);
}

public Drawable getCurrent() {
    return this.mDrawable.getCurrent();
}

public boolean setVisible(boolean visible, boolean restart) {
    return super.setVisible(visible, restart) || this.mDrawable.setVisible(visible, restart);
}

public int getOpacity() {
    return this.mDrawable.getOpacity();
}

public Region getTransparentRegion() {
    return this.mDrawable.getTransparentRegion();
}

public int getIntrinsicWidth() {
    return this.mDrawable.getIntrinsicWidth();
}

public int getIntrinsicHeight() {
    return this.mDrawable.getIntrinsicHeight();
}

public int getMinimumWidth() {
    return this.mDrawable.getMinimumWidth();
}

public int getMinimumHeight() {
    return this.mDrawable.getMinimumHeight();
}

public boolean getPadding(Rect padding) {
    return this.mDrawable.getPadding(padding);
}

public void invalidateDrawable(Drawable who) {
    this.invalidateSelf();
}

public void scheduleDrawable(Drawable who, Runnable what, long when) {
    this.scheduleSelf(what, when);
}

public void unscheduleDrawable(Drawable who, Runnable what) {
    this.unscheduleSelf(what);
}

protected boolean onLevelChange(int level) {
    return this.mDrawable.setLevel(level);
}

public void setAutoMirrored(boolean mirrored) {
    DrawableCompat.setAutoMirrored(this.mDrawable, mirrored);
}

public boolean isAutoMirrored() {
    return DrawableCompat.isAutoMirrored(this.mDrawable);
}

public void setTint(int tint) {
    DrawableCompat.setTint(this.mDrawable, tint);
}

public void setTintList(ColorStateList tint) {
    DrawableCompat.setTintList(this.mDrawable, tint);
}

public void setTintMode(PorterDuff.Mode tintMode) {
    DrawableCompat.setTintMode(this.mDrawable, tintMode);
}

public void setHotspot(float x, float y) {
    DrawableCompat.setHotspot(this.mDrawable, x, y);
}

public void setHotspotBounds(int left, int top, int right, int bottom) {
    DrawableCompat.setHotspotBounds(this.mDrawable, left, top, right, bottom);
}

public Drawable getWrappedDrawable() {
    return this.mDrawable;
}

public void setWrappedDrawable(Drawable drawable) {
    if (this.mDrawable != null) {
        this.mDrawable.setCallback((Callback)null);
    }

    this.mDrawable = drawable;
    if (drawable != null) {
        drawable.setCallback(this);
    }

}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271