RoundShadowImageView
RoundShadowImageView 是 1 个为圆形图片的 ImageView 增加暗影的自定义控件.
GitHub
RoundShadowImageView
为什么写这个库
- 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();}
}
}
参考文章
- 问题 0011 – Android 暗影 轮廓 Outline
喜爱的同学点个 star 哈!! RoundShadowImageView