对于圆角、渐变色、阴影布局这些元素,第一想法是使用创建一个shape来实现,但是不便于管理,最重要的是取名还困难,所以就冒出了一个使用布局来实现的想法。
PS:先附上该自定义控件实现后的效果图,所看到的太阳、地球、月亮、星星,都是用该控件实现的
控件代码:此处是继承自布局,对于不想要套一层布局的需求,可以继承自己想要的控件,然后把public void dispatchDraw(Canvas canvas);方法替换成public void onDraw(Canvas canvas);方法,记得把super(Canvas canvas);放到方法的最后,不然内容会被遮挡。
public class StyleLayout extends ConstraintLayout {
private StyleViewAttr styleViewAttr;
private Path shadowPath;//阴影路径
private RectF backgroundRectF;//背景范围,用于计算背景路径
private Path backgroundPath;//背景路径,包含绘制标准,即相对这条路径来计算绘制范围
private RectF borderRectF;//边框范围,用于计算边框路径
private Path borderPath;//边框路径
private Shader backgroundGradient;//渐变背景色对象
private Paint mPaint;//画笔
private float shadowRadioArr[] = new float[8];
private float radioArr[] = new float[8];
public StyleLayout(Context context) {
super(context);
init(context, null);
}
public StyleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public StyleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
public void setStyleViewAttr(StyleViewAttr styleViewAttr) {
this.styleViewAttr = styleViewAttr;
//圆角,参考Path.addRoundRect()的api说明, clearBurr是用于去掉阴影是圆角的时候的毛边
int clearBurr = 6;
float roundAttrArr[] = new float[]{styleViewAttr.left_top_round, styleViewAttr.right_top_round, styleViewAttr.right_bottom_round, styleViewAttr.left_bottom_round};
for(int i = 0; i < shadowRadioArr.length; i += 2){
float roundValue = roundAttrArr[i / 2];
shadowRadioArr[i] = roundValue + clearBurr;
shadowRadioArr[i + 1] = roundValue + clearBurr;
radioArr[i] = roundValue;
radioArr[i + 1] = roundValue;
}
}
private void init(Context context, AttributeSet attrs){
shadowPath = new Path();
backgroundPath = new Path();
backgroundRectF = new RectF();
borderPath = new Path();
borderRectF = new RectF();
mPaint = new Paint();
styleViewAttr = new StyleViewAttr(context, attrs);
setStyleViewAttr(styleViewAttr);
//去掉阴影和边框占据的空间
int backgroundStartX = (int) (styleViewAttr.shadowWidth - getShadowOffsetWidth());
int backgroundStartY = (int) (styleViewAttr.shadowWidth - getShadowOffsetHeight());
setPadding(backgroundStartX + getPaddingStart(), backgroundStartY + getPaddingTop(), backgroundStartX + getPaddingEnd(), backgroundStartY + getPaddingBottom());
}
@Override
public void dispatchDraw(Canvas canvas) {
//抗锯齿
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
//布局没有变动的时候,不需要重新计算
if(getWidth() != backgroundRectF.right + styleViewAttr.borderWidth + styleViewAttr.shadowWidth
|| getWidth() != backgroundRectF.bottom + styleViewAttr.borderWidth + styleViewAttr.shadowWidth) {
computerRectF();
//清除已添加的路径
backgroundPath.rewind();
shadowPath.rewind();
borderPath.rewind();
}
//绘制阴影
drawShadow(canvas);
//绘制背景色
drawBackgroundGradient(canvas);
//绘制边框线
drawBorder(canvas);
super.dispatchDraw(canvas);
}
private void computerRectF(){
//空出阴影的绘制范围,加入了阴影的偏移量计算,当偏移量等于阴影宽度,背景色不会被遮挡
float backgroundStartX = styleViewAttr.shadowWidth - getShadowOffsetWidth();
float backgroundStartY = styleViewAttr.shadowWidth - getShadowOffsetHeight();
float backgroundWidth = getWidth() - styleViewAttr.shadowWidth * 2;
float backgroundHeight = getHeight() - styleViewAttr.shadowWidth * 2;
//设置背景色的绘制范围
backgroundRectF.set(backgroundStartX, backgroundStartY, backgroundStartX + backgroundWidth, backgroundHeight + backgroundStartY);
//创建背景色
backgroundGradient = new LinearGradient(getGradientStartX(), //起始位置的X坐标
getGradientStartY(), //起始位置的Y坐标
getGradientEndX(), //终止位置的X坐标
getGradientEndY(),//终止位置的Y坐标
styleViewAttr.bgGradientColor,//渐变颜色数组,根据数组的顺序进行渲染
styleViewAttr.bgcGradientWeight,//比重,设置每种颜色在总宽度中的比重,不设置则每种颜色的比重相等
Shader.TileMode.CLAMP);//设置平铺模式
//在空出阴影的基础上,再空出边框的绘制范围
float borderStartX = backgroundStartX + styleViewAttr.borderWidth / 2f;
float borderStartY = backgroundStartY + styleViewAttr.borderWidth / 2f;
float borderWidth = backgroundWidth - styleViewAttr.borderWidth;
float borderHeight = backgroundHeight - styleViewAttr.borderWidth;
//设置边框的绘制范围
borderRectF.set(borderStartX, borderStartY, borderStartX + borderWidth, borderStartY + borderHeight);
}
private float getShadowOffsetWidth(){
return styleViewAttr.shadowOffsetX * styleViewAttr.shadowWidth;
}
private float getShadowOffsetHeight(){
return styleViewAttr.shadowOffsetY * styleViewAttr.shadowWidth;
}
/**绘制阴影
* @param canvas
*/
public void drawShadow(Canvas canvas){
if(styleViewAttr.shadowWidth > 0) {
//不使用shadowPath进行绘制的原因,是因为圆角会有黑色的毛边
if(shadowPath.isEmpty()) {
shadowPath.addRoundRect(backgroundRectF, shadowRadioArr, Path.Direction.CW);
}
mPaint.reset();
mPaint.setShadowLayer(styleViewAttr.shadowWidth, getShadowOffsetWidth(), getShadowOffsetHeight(), styleViewAttr.shadowColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawPath(shadowPath, mPaint);
}
}
/**绘制渐变色背景
*
*/
private void drawBackgroundGradient(Canvas canvas){
if(backgroundPath.isEmpty()) {
backgroundPath.addRoundRect(backgroundRectF, radioArr, Path.Direction.CW);
}
mPaint.reset();
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setShader(backgroundGradient);
canvas.drawPath(backgroundPath, mPaint);
}
/**绘制边框
* @param canvas
*/
private void drawBorder(Canvas canvas) {
if(styleViewAttr.borderWidth > 0) {
//不使用backgroundPath进行绘制的原因,是因为描边是沿着路径的两侧绘制的
if(borderPath.isEmpty()) {
borderPath.addRoundRect(borderRectF, radioArr, Path.Direction.CW);
}
mPaint.reset();
mPaint.setColor(styleViewAttr.borderColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(styleViewAttr.borderWidth);
canvas.drawPath(borderPath, mPaint);
}
}
private float getGradientStartX(){
if(styleViewAttr.bgcGradientAngle <= 45){
return backgroundRectF.left;
}else if(styleViewAttr.bgcGradientAngle < 90){
return backgroundRectF.centerX() - getOtherSideLength(backgroundRectF.centerY());
}else {
return backgroundRectF.centerX();
}
}
private float getGradientStartY(){
if(styleViewAttr.bgcGradientAngle <= 45){
return backgroundRectF.centerY() - getOtherSideLength(backgroundRectF.centerX());
}else if(styleViewAttr.bgcGradientAngle < 90){
return backgroundRectF.top;
}else {
return backgroundRectF.top;
}
}
private float getGradientEndX(){
if(styleViewAttr.bgcGradientAngle <= 45){
return backgroundRectF.right;
}else if(styleViewAttr.bgcGradientAngle < 90){
return backgroundRectF.centerX() + getOtherSideLength(backgroundRectF.centerY());
}else {
return backgroundRectF.centerX();
}
}
private float getGradientEndY(){
if(styleViewAttr.bgcGradientAngle <= 45){
return backgroundRectF.centerY() + getOtherSideLength(backgroundRectF.centerX());
}else if(styleViewAttr.bgcGradientAngle < 90){
return backgroundRectF.bottom;
}else {
return backgroundRectF.bottom;
}
}
/**以背景色范围的中心,计算x轴半径与y轴半径的比值,
* 当一条直角边固定的情况下,根据角度计算另一条直角边的长度
* @return
*/
private float getOtherSideLength(float fixedSide){
//两条直角边的比例
float tan = (float) Math.tan(Math.PI * styleViewAttr.bgcGradientAngle / 180);
//计算正切值
float scale = backgroundRectF.centerY() / backgroundRectF.centerX();
//返回另一条直角边的长度
return tan * fixedSide * scale;
}
public static class StyleViewAttr {
public static final int SHADOW_LEFT_OFFSET = -1;//阴影偏移到左边
public static final int SHADOW_VERTICAL_MIDDLE= 0;//阴影不偏移
public static final int SHADOW_RIGHT_OFFSET= 1;//阴影偏移到右边
public static final int SHADOW_TOP_OFFSET= -1;//阴影偏移到上方
public static final int SHADOW_HORIZONTAL_MIDDLE = 0;//阴影不偏移
public static final int SHADOW_BOTTOM_OFFSET= 1;//阴影偏移到下方
/**圆角位置*/
private float left_top_round;//左上角
private float left_bottom_round;//左下角
private float right_top_round;//右上角
private float right_bottom_round;//右下角
/**背景渐变色,可设置多个颜色,根据设置的方向自动渐变,
* xml中使用bgGradientStartColor、bgGradientEndColor设置起始和结束颜色,不支持传递数组*/
private int[] bgGradientColor;
/**背景渐变色方向 角度[0°-90°]*/
private float bgcGradientAngle;
/**每种渐变色在整个控件中的比重*/
private float[] bgcGradientWeight;
/**边框宽度*/
private int borderWidth;
/**边框颜色*/
private int borderColor;
/**阴影颜色 */
private int shadowColor;
/**阴影宽度*/
private int shadowWidth;
/**阴影水平偏移距离,当偏移量等于阴影宽度,背景色不会被遮挡*/
private int shadowOffsetX;
/**阴影垂直偏移距离,当偏移量等于阴影宽度,背景色不会被遮挡*/
private int shadowOffsetY;
public StyleViewAttr(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.StyleLayout);
int all_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_all_round, 0);
left_top_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_left_top_round, all_round);
left_bottom_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_left_bottom_round, all_round);
right_top_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_right_top_round, all_round);
right_bottom_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_right_bottom_round, all_round);
int startColor = typedArray.getColor(R.styleable.StyleLayout_bgGradientStartColor, 0);
int endColor = typedArray.getColor(R.styleable.StyleLayout_bgGradientEndColor, 0);
float startWeight = typedArray.getFloat(R.styleable.StyleLayout_bgGradientStartWeight, 0f);
float endWeight = typedArray.getFloat(R.styleable.StyleLayout_bgGradientEndWeight, 1f);
borderWidth = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_borderWidth, 0);
borderColor = typedArray.getColor(R.styleable.StyleLayout_borderColor, Color.TRANSPARENT);
bgcGradientAngle = typedArray.getFloat(R.styleable.StyleLayout_bgcGradientAngle, 0);
shadowColor = typedArray.getColor(R.styleable.StyleLayout_shadowColor, Color.GRAY);
shadowWidth = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_shadowWidth, 0);
shadowOffsetX = typedArray.getInt(R.styleable.StyleLayout_shadowOffsetX, SHADOW_VERTICAL_MIDDLE);
shadowOffsetY = typedArray.getInt(R.styleable.StyleLayout_shadowOffsetY, SHADOW_HORIZONTAL_MIDDLE);
typedArray.recycle();
if(startColor != 0 && endColor != 0){
bgGradientColor = new int[]{startColor, endColor};
}else if(startColor != 0){
bgGradientColor = new int[]{startColor, startColor};
}else {
bgGradientColor = new int[]{endColor, endColor};
}
bgcGradientWeight = new float[]{startWeight, endWeight};
shadowWidth = Math.max(shadowWidth, 0);
}
public StyleViewAttr() {
}
public StyleViewAttr setLeft_top_round(float left_top_round) {
this.left_top_round = left_top_round;
return this;
}
public StyleViewAttr setLeft_bottom_round(float left_bottom_round) {
this.left_bottom_round = left_bottom_round;
return this;
}
public StyleViewAttr setRight_top_round(float right_top_round) {
this.right_top_round = right_top_round;
return this;
}
public StyleViewAttr setRight_bottom_round(float right_bottom_round) {
this.right_bottom_round = right_bottom_round;
return this;
}
public StyleViewAttr setAllRound(float round){
if(left_top_round == 0){
left_top_round = round;
}
if(right_top_round == 0){
right_top_round = round;
}
if(left_bottom_round == 0){
left_bottom_round = round;
}
if(right_bottom_round == 0){
right_bottom_round = round;
}
return this;
}
public StyleViewAttr setBgGradientColor(int...bgGradientColor) {
if(bgGradientColor == null || bgGradientColor.length == 0){
this.bgGradientColor = null;
}else if(bgGradientColor.length == 1){
this.bgGradientColor = new int[]{bgGradientColor[0], bgGradientColor[0]};
}else {
this.bgGradientColor = new int[bgGradientColor.length];
System.arraycopy(bgGradientColor, 0, this.bgGradientColor, 0, bgGradientColor.length);
}
return this;
}
public StyleViewAttr setBgcGradientAngle(float bgcGradientAngle) {
if(bgcGradientAngle < 0) throw new IllegalArgumentException("角度不能小于0度");
if(bgcGradientAngle > 90) throw new IllegalArgumentException("角度不能大于90度");
this.bgcGradientAngle = bgcGradientAngle;
return this;
}
public StyleViewAttr setBgcGradientWeight(float[] bgcGradientWeight) {
this.bgcGradientWeight = bgcGradientWeight;
return this;
}
public StyleViewAttr setBorderWidth(int borderWidth) {
this.borderWidth = borderWidth;
return this;
}
public StyleViewAttr setBorderColor(int borderColor) {
this.borderColor = borderColor;
return this;
}
public StyleViewAttr setShadowColor(int shadowColor) {
this.shadowColor = shadowColor;
return this;
}
public StyleViewAttr setShadowWidth(int shadowWidth) {
if(bgcGradientAngle < 0) throw new IllegalArgumentException("阴影宽度不能为负数");
this.shadowWidth = shadowWidth;
return this;
}
public StyleViewAttr setShadowOffsetX(int shadowOffsetX) {
this.shadowOffsetX = shadowOffsetX;
return this;
}
public StyleViewAttr setShadowOffsetY(int shadowOffsetY) {
this.shadowOffsetY = shadowOffsetY;
return this;
}
}
}
在attrs中设置的属性:
<declare-styleable name="StyleLayout">
<!--对应位置的圆角大小-->
<attr name="left_top_round" format="dimension"/>
<attr name="left_bottom_round" format="dimension"/>
<attr name="right_top_round" format="dimension"/>
<attr name="right_bottom_round" format="dimension"/>
<attr name="all_round" format="dimension"/>
<!--背景渐变色之开头颜色-->
<attr name="bgGradientStartColor" format="color"/>
<!--背景渐变色之结尾颜色-->
<attr name="bgGradientEndColor" format="color"/>
<!--背景渐变色之开头颜色在整个控件中的比重-->
<attr name="bgGradientStartWeight" format="float"/>
<!--背景渐变色之结尾颜色在整个控件中的比重-->
<attr name="bgGradientEndWeight" format="float"/>
<!--背景渐变色方向 角度[0°-90°]-->
<attr name="bgcGradientAngle" format="float"/>
<!--边框宽度-->
<attr name="borderWidth" format="dimension"/>
<!--边框颜色-->
<attr name="borderColor" format="color"/>
<!--阴影颜色-->
<attr name="shadowColor" format="color"/>
<!--阴影宽度-->
<attr name="shadowWidth" format="dimension"/>
<!--阴影水平偏移-->
<attr name="shadowOffsetX" format="enum">
<enum name="left_offset" value="-1"/>
<enum name="vertical_middle" value="0"/>
<enum name="right_offset" value="1"/>
</attr>
<!--阴影垂直偏移-->
<attr name="shadowOffsetY" format="enum">
<enum name="top_offset" value="-1"/>
<enum name="horizontal_middle" value="0"/>
<enum name="bottom_offset" value="1"/>
</attr>
</declare-styleable>
|