RoundShadowImageView - 圆形图片的阴影,自由定制!

2020-11-25 00:29:54 +08:00
 huanhailiuxin

RoundShadowImageView

RoundShadowImageView 是 1 个为圆形图片的 ImageView 添加阴影的自定义控件.

GitHub

RoundShadowImageView

为什么写这个库

  1. Android 未提供现成的工具,自定义控件阴影的颜色
  2. 开源社区中现有的库,使用了 ViewGroup 包装子 View 的形式,会增加布局层级
  3. 使用 Paint.setShadowLayer,颜色的透明度变化太快,只能在很窄的范围能看到颜色渐变

RoundShadowImageView 的优势

  1. 不增加布局层级,性能相对更好
  2. 阴影的颜色,初始透明度,位置,相对中心点角度,阴影的显示尺寸 均可自由定制.

RoundShadowImageView 的局限

适用范围较窄,仅适用于为圆形图片 ImageView 定制阴影.

使用步骤:

步骤 1:

将源码拷贝至你的项目.

步骤 2:

在布局文件中声明,或者直接通过 java 代码创建 RoundShadowImageView 实例.

步骤 3:

在 xml 中直接设置其阴影相关属性,或通过 java 方法进行设置.

示例:

源码:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RoundShadowImageView">
        <!--阴影宽度相对于内容区域半径的比例-->
        <attr name="shadowRatio" format="float" />
        <!--阴影中心相对于内容区域中心的角度,以内容区域垂直向下为 0 度 /起始角度-->
        <attr name="shadowCircleAngle" format="float" />
        <!--阴影颜色-->
        <attr name="shadowColor" format="color|reference" />
        <!--阴影颜色初始透明度-->
        <attr name="shadowStartAlpha" format="float" />
        <!--阴影位置-->
        <attr name="shadowPosition" format="enum">
            <enum name="start" value="1" />
            <enum name="top" value="2" />
            <enum name="end" value="3" />
            <enum name="bottom" value="4" />
        </attr>
    </declare-styleable>
</resources>
import static com.huanhailiuxin.jet2020.othertest.shadow.ShadowPosition.BOTTOM;
import static com.huanhailiuxin.jet2020.othertest.shadow.ShadowPosition.END;
import static com.huanhailiuxin.jet2020.othertest.shadow.ShadowPosition.START;
import static com.huanhailiuxin.jet2020.othertest.shadow.ShadowPosition.TOP;
@IntDef({
        START,
        TOP,
        END,
        BOTTOM
})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@interface ShadowPosition {
    int START = 1;
    int TOP = 2;
    int END = 3;
    int BOTTOM = 4;
}

/**
 * @author HuanHaiLiuXin
 * @github https://github.com/HuanHaiLiuXin
 * @date 2020/11/23
 */
public class RoundShadowImageView extends AppCompatImageView {
    private Paint paint;
    private Shader shader;
    int[] colors;
    float[] stops;
    private float contentSize;
    @FloatRange(from = 0.0F, to = 1.0F)
    private float shadowRatio = 0.30F;
    private float shadowRadius = 0.0F;
    private float shadowCenterX, shadowCenterY;
    @ShadowPosition
    private int shadowPosition = ShadowPosition.BOTTOM;
    private float shadowCircleAngle = 0F;
    private boolean useShadowCircleAngle = false;
    private int red, green, blue;
    private int shadowColor = Color.RED;
    private @FloatRange(from = 0F, to = 1F)
    float shadowStartAlpha = 0.5F;
    private boolean isLtr = true;

    public RoundShadowImageView(Context context) {
        this(context, null, 0);
    }

    public RoundShadowImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RoundShadowImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
    }

    private void initAttrs(Context context, @Nullable AttributeSet attrs) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL);
        isLtr = getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundShadowImageView);
            shadowRatio = typedArray.getFloat(R.styleable.RoundShadowImageView_shadowRatio, shadowRatio);
            shadowCircleAngle = typedArray.getFloat(R.styleable.RoundShadowImageView_shadowCircleAngle, shadowCircleAngle);
            if (shadowCircleAngle > 0F) {
                useShadowCircleAngle = true;
            }
            if (!useShadowCircleAngle) {
                shadowPosition = typedArray.getInt(R.styleable.RoundShadowImageView_shadowPosition, shadowPosition);
            }
            shadowColor = typedArray.getColor(R.styleable.RoundShadowImageView_shadowColor, shadowColor);
            gainRGB();
            shadowStartAlpha = typedArray.getFloat(R.styleable.RoundShadowImageView_shadowStartAlpha, shadowStartAlpha);
            typedArray.recycle();
        }
    }

    private void gainRGB() {
        red = Color.red(shadowColor);
        green = Color.green(shadowColor);
        blue = Color.blue(shadowColor);
    }

    private void gainShadowCenterAndShader() {
        gainShadowCenter();
        gainShader();
    }

    private void gainShadowCenter() {
        shadowRadius = contentSize / 2F;
        if (useShadowCircleAngle) {
            double radians = Math.toRadians(shadowCircleAngle + 90);
            shadowCenterX = (float) (getWidth() / 2 + Math.cos(radians) * shadowRadius * shadowRatio);
            shadowCenterY = (float) (getHeight() / 2 + Math.sin(radians) * shadowRadius * shadowRatio);
        } else {
            switch (shadowPosition) {
                case ShadowPosition.START:
                    if (isLtr) {
                        shadowCenterX = getWidth() / 2 - shadowRadius * shadowRatio;
                    } else {
                        shadowCenterX = getWidth() / 2 + shadowRadius * shadowRatio;
                    }
                    shadowCenterY = getHeight() / 2;
                    break;
                case ShadowPosition.TOP:
                    shadowCenterY = getHeight() / 2 - shadowRadius * shadowRatio;
                    shadowCenterX = getWidth() / 2;
                    break;
                case ShadowPosition.END:
                    if (isLtr) {
                        shadowCenterX = getWidth() / 2 + shadowRadius * shadowRatio;
                    } else {
                        shadowCenterX = getWidth() / 2 - shadowRadius * shadowRatio;
                    }
                    shadowCenterY = getHeight() / 2;
                    break;
                case ShadowPosition.BOTTOM:
                    shadowCenterY = getHeight() / 2 + shadowRadius * shadowRatio;
                    shadowCenterX = getWidth() / 2;
                    break;
                default:
                    shadowCenterY = getHeight() / 2 + shadowRadius * shadowRatio;
                    shadowCenterX = getWidth() / 2;
                    break;
            }
        }
    }

    private void gainShader() {
        colors = new int[]{
                Color.TRANSPARENT,
                Color.argb((int) (shadowStartAlpha * 255), red, green, blue),
                Color.argb((int) (shadowStartAlpha * 255 / 2), red, green, blue),
                Color.argb(0, red, green, blue)
        };
        stops = new float[]{
                (1F - shadowRatio) * 0.95F,
                1F - shadowRatio,
                1F - shadowRatio * 0.50F,
                1F
        };
        shader = new RadialGradient(shadowCenterX, shadowCenterY, shadowRadius, colors, stops, Shader.TileMode.CLAMP);
    }

    private void contentSizeChanged() {
        contentSize = Math.min(getWidth(), getHeight()) / (1 + this.shadowRatio);
        setPadding((int) (getWidth() - contentSize) / 2, (int) (getHeight() - contentSize) / 2, (int) (getWidth() - contentSize) / 2, (int) (getHeight() - contentSize) / 2);
        gainShadowCenterAndShader();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        contentSizeChanged();
    }

    public void setShadowRatio(@FloatRange(from = 0.0F, to = 1.0F) float shadowRatio) {
        shadowRatio = shadowRatio % 1F;
        if (shadowRatio != this.shadowRatio) {
            this.shadowRatio = shadowRatio;
            contentSizeChanged();
            invalidate();
        }
    }

    public void setShadowColor(@ColorInt int shadowColor) {
        if (shadowColor != this.shadowColor) {
            this.shadowColor = shadowColor;
            gainRGB();
            gainShader();
            invalidate();
        }
    }

    public void setShadowStartAlpha(@FloatRange(from = 0F, to = 1F) float shadowStartAlpha) {
        shadowStartAlpha = shadowStartAlpha % 1F;
        if (shadowStartAlpha != this.shadowStartAlpha) {
            this.shadowStartAlpha = shadowStartAlpha;
            gainShader();
            invalidate();
        }
    }

    public void setShadowCircleAngle(float shadowCircleAngle) {
        shadowCircleAngle = Math.abs(shadowCircleAngle) % 360.0F;
        if (shadowCircleAngle != this.shadowCircleAngle) {
            this.shadowCircleAngle = shadowCircleAngle;
            if (this.shadowCircleAngle > 0F) {
                useShadowCircleAngle = true;
            }
            gainShadowCenterAndShader();
            invalidate();
        }
    }

    public void setShadowPosition(@ShadowPosition int shadowPosition){
        if(useShadowCircleAngle || shadowPosition != this.shadowPosition){
            useShadowCircleAngle = false;
            this.shadowPosition = shadowPosition;
            gainShadowCenterAndShader();
            invalidate();
        }
    }

    public float getShadowRatio() {
        return shadowRatio;
    }

    public float getShadowCircleAngle() {
        return shadowCircleAngle;
    }

    public int getShadowColor() {
        return shadowColor;
    }

    public float getShadowStartAlpha() {
        return shadowStartAlpha;
    }

    public int getShadowPosition() {
        return shadowPosition;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        paint.setShader(shader);
        canvas.drawCircle(shadowCenterX, shadowCenterY, shadowRadius, paint);
        paint.setShader(null);
        super.onDraw(canvas);
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        boolean newLtr = getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
        if (newLtr != isLtr) {
            this.isLtr = newLtr;
            gainShadowCenterAndShader();
            invalidate();
        }
    }
}

参考文章


喜欢的同学点个 star 哈!! RoundShadowImageView

501 次点击
所在节点    分享创造
1 条回复
Cabana
2020-11-26 09:06:45 +08:00
正常情况下用 shape 作为 background 然后 setcliptooutline 为 true 可以解决大部分自定义剪裁和阴影问题。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/728910

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX