RoundShadowImageView
RoundShadowImageView 是1个为圆形图片的ImageView添加阴影的自定义控件.
GitHub
为什么写这个库
- Android未提供现成的工具,自定义控件阴影的颜色
- 开源社区中现有的库,使用了ViewGroup包装子View的形式,会增加布局层级
- 使用Paint.setShadowLayer,颜色的透明度变化太快,只能在很窄的范围能看到颜色渐变
RoundShadowImageView的优势
- 不增加布局层级,性能相对更好
- 阴影的颜色,初始透明度,位置,相对中心点角度,阴影的显示尺寸 均可自由定制.
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