乐趣区

关于android:RoundShadowImageView-圆形图片的阴影自由定制

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();}
    }
}

参考文章

  • 问题 0011 – Android 暗影 轮廓 Outline

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

退出移动版