Android开发中最烦的事,就是不停的定义不同角度不同颜色不同样式的边框,不仅很难重复使用,也很难命名管理
现有的样式实现方式大体分为以下几类
1.图片背景代替 (缺点:图片命名难以管理,且适配会存在变形)
2.drawable (缺点:命名难以管理,难以复用)
3.自定义view(缺点:太烦)
4.第三方框架(PS: 实现思路基本还是第三种,缺点:太烦)
需求场景
需要一个只有一个右上圆弧为3dp的红色背景
需要一个只有一个左上圆弧为3dp的蓝色背景
需要一个只有一个右上左上圆弧为5dp的灰色背景
右上左上圆弧为3dp蓝色背景
等等等等....
想实现的效果
最近学iOS,对iOS上实现这个功能的简单羡慕不已,Android想实现以下的效果
view.setRadius(strokeWidth = 10f,strokeColor = R.color.redF45265,backgroundColor = R.color.color999999,leftBottomRadius = 10,leftTopRadius = 40,rightBottomRadius = 20,rightTopRadius = 30)
view.setRadius(strokeWidth = 0.6f, strokeColor = R.color.colorCCCCCC, radius = 3)
view1.setRadius(strokeWidth = 0.6f, strokeColor = R.color.colorCCCCCC, radius = 3)
view1.setShadow(shadowColor = R.color.redF45265,shadowWidth = 4f)
代码实现
自定义一个Drawable类,在里面重写部分代码,边框这些用paint画出来(canvas小白,又更好的办法,欢迎指出)
class CustomDrawable(val view: View) : Drawable() {
private var leftTopRadius = 0f
private var rightTopRadius = 0f
private var leftBottomRadius = 0f
private var rightBottomRadius = 0f
private var radiusArray = floatArrayOf()
private var shadowColor: Int = R.color.colorPrimaryDark.getColor()
private var shadowRectF = RectF() // 阴影部分的背景颜色
private var shadowWidth = 0f // 阴影宽度单位为dp 0为不需要阴影
private var strokeWidth = 0f
private var strokeColor: Int = R.color.color999999.getColor()
private var backGroundColor: Int = R.color.white.getColor() // 背景颜色
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
/**
* 设置圆角
* @param backGroundColor 背景色
* @param leftTopRadius 左上角
* @param leftBottomRadius 左下角
* @param rightBottomRadius 右下角
* @param rightTopRadius 右上角
* @param strokeWidth 边框宽度 dp f
* @param strokeColor 边框颜色
*/
fun setRadius(
backGroundColor: Int = R.color.white,
strokeWidth: Float = 0f,
strokeColor: Int = R.color.colorB3B3B3,
leftTopRadius: Int = 0,
rightTopRadius: Int = 0,
rightBottomRadius: Int = 0,
leftBottomRadius: Int = 0
) {
clearColorFilter()
this.leftBottomRadius = leftBottomRadius.dp2px.toFloat()
this.leftTopRadius = leftTopRadius.dp2px.toFloat()
this.rightTopRadius = rightTopRadius.dp2px.toFloat()
this.rightBottomRadius = rightBottomRadius.dp2px.toFloat()
this.strokeWidth = Screen.dp2Px(strokeWidth).toFloat()
this.strokeColor = strokeColor.getColor()
this.backGroundColor = backGroundColor.getColor()
}
/**
* 设置阴影
* @param shadowWidth 阴影宽度 dp f
* @param shadowColor 阴影颜色
*/
fun setShadow(
shadowWidth: Float = 0f,
shadowColor: Int = R.color.colorB3B3B3
) {
this.shadowWidth = Screen.dp2Px(shadowWidth).toFloat()
this.shadowColor = shadowColor.getColor()
}
fun invalidate() {
invalidateSelf()
}
override fun draw(canvas: Canvas) {
getRadius()
setDraw(canvas)
}
private fun setDraw(canvas: Canvas) {
paint.color = strokeColor
paint.isAntiAlias = true
paint.style = Paint.Style.STROKE
paint.strokeWidth = strokeWidth + if (backGroundColor != strokeColor) strokeWidth else 0f
paint.setShadowLayer(shadowWidth, 0f, 0f, shadowColor)
val path = Path()
path.addRoundRect(shadowRectF, radiusArray, Path.Direction.CW)
canvas.drawPath(path, paint)
// 渲染内部
paint.reset()
paint.color = backGroundColor
paint.isAntiAlias = true
paint.style = Paint.Style.FILL
paint.strokeWidth = strokeWidth
path.reset()
path.addRoundRect(shadowRectF, radiusArray, Path.Direction.CW)
canvas.drawPath(path, paint)
}
/**
* 计算圆角等值
*/
private fun getRadius() {
radiusArray = floatArrayOf(
leftTopRadius, leftTopRadius,
rightTopRadius, rightTopRadius,
rightBottomRadius, rightBottomRadius,
leftBottomRadius, leftBottomRadius
)
shadowRectF = RectF(
shadowWidth + strokeWidth,
shadowWidth + strokeWidth,
view.width - shadowWidth - strokeWidth,
view.height - shadowWidth - strokeWidth
)
}
override fun setAlpha(alpha: Int) {
}
override fun setColorFilter(colorFilter: ColorFilter?) {
}
override fun getOpacity(): Int {
return PixelFormat.TRANSLUCENT
}
}
至此自定义部分已经完成了,接下来看调用
fun View.setShadow(
shadowWidth: Float = 0f,
shadowColor: Int = R.color.colorB3B3B3
) {
if (this is CustomImageView) {
setImageViewShadow(shadowWidth,shadowColor)
return
}
var newDrawable: CustomDrawable? = null
// 如果类型是自定义类型drawable 直接修改
if (backgroundDrawable != null && backgroundDrawable is CustomDrawable) {
newDrawable = backgroundDrawable as CustomDrawable
}
// drawable类型不是自定义的CustomDrawable类型,则修改为CustomDrawable
if (newDrawable == null) {
newDrawable = CustomDrawable(this)
}
newDrawable.setShadow(shadowWidth, shadowColor)
newDrawable.invalidate()
background = newDrawable
}
fun View.setRadius(
backgroundColor: Int = R.color.white,
strokeWidth: Float = 0f,
strokeColor: Int = R.color.colorB3B3B3,
leftTopRadius: Int = 0,
rightTopRadius: Int = 0,
leftBottomRadius: Int = 0,
rightBottomRadius: Int = 0
) {
if (this is CustomImageView) {
setImageViewRadius(
backGroundColor = backgroundColor,
strokeWidth = strokeWidth,
strokeColor = strokeColor,
leftTopRadius = leftTopRadius,
rightTopRadius = rightTopRadius,
leftBottomRadius = leftBottomRadius,
rightBottomRadius = rightBottomRadius
)
return
}
var newDrawable: CustomDrawable? = null
val oldDrawable = backgroundDrawable
if (backgroundDrawable != null && oldDrawable is CustomDrawable) {
newDrawable = oldDrawable
}
if (newDrawable == null) {
newDrawable = CustomDrawable(this)
}
newDrawable.setRadius(
backgroundColor,
strokeWidth,
strokeColor,
leftTopRadius,
rightTopRadius,
rightBottomRadius,
leftBottomRadius
)
newDrawable.invalidate()
// 重新渲染 防止颜色叠加
requestLayout()
background = newDrawable
}
/**
* 添加圆角
*/
fun View.setRadius(
backgroundColor: Int = R.color.white,
strokeWidth: Float = 0f,
strokeColor: Int = backgroundColor,
radius: Int = 0
) {
// iamgeView 图层比较特殊,是浮在drawable之上,需要特殊处理 也可以直接调用
if (this is CustomImageView) {
setImageViewRadius(
backGroundColor = backgroundColor,
strokeWidth = strokeWidth,
strokeColor = strokeColor,
leftTopRadius = leftTopRadius,
rightTopRadius = rightTopRadius,
leftBottomRadius = leftBottomRadius,
rightBottomRadius = rightBottomRadius
)
return
}
setRadius(
backgroundColor = backgroundColor,
strokeWidth = strokeWidth,
strokeColor = strokeColor,
leftTopRadius = radius,
rightTopRadius = radius,
leftBottomRadius = radius,
rightBottomRadius = radius
)
}
自定义边框各圆角的imageView
class CustomImageView: ImageView {
private var leftTopRadius = 0f
private var rightTopRadius = 0f
private var leftBottomRadius = 0f
private var rightBottomRadius = 0f
private var radiusArray = floatArrayOf()
private var shadowColor: Int = R.color.colorPrimaryDark.getColor()
private var shadowRectF = RectF() // 阴影部分的背景颜色
private var shadowWidth = 0f // 阴影宽度单位为dp 0为不需要阴影
private var strokeWidth = 0f
private var strokeColor: Int = R.color.color999999.getColor()
private var backGroundColor: Int = R.color.white.getColor() // 背景颜色
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
/**
* 设置圆角
* @param backGroundColor 背景色
* @param leftTopRadius 左上角
* @param leftBottomRadius 左下角
* @param rightBottomRadius 右下角
* @param rightTopRadius 右上角
* @param strokeWidth 边框宽度 dp f
* @param strokeColor 边框颜色
*/
fun setImageViewRadius(
backGroundColor: Int = R.color.white,
strokeWidth: Float = 0f,
strokeColor: Int = R.color.colorB3B3B3,
leftTopRadius: Int = 0,
rightTopRadius: Int = 0,
rightBottomRadius: Int = 0,
leftBottomRadius: Int = 0
) {
clearColorFilter()
this.leftBottomRadius = leftBottomRadius.dp2px.toFloat()
this.leftTopRadius = leftTopRadius.dp2px.toFloat()
this.rightTopRadius = rightTopRadius.dp2px.toFloat()
this.rightBottomRadius = rightBottomRadius.dp2px.toFloat()
this.strokeWidth = Screen.dp2Px(strokeWidth).toFloat()
this.strokeColor = strokeColor.getColor()
this.backGroundColor = backGroundColor.getColor()
}
/**
* 设置阴影
* @param shadowWidth 阴影宽度 dp f
* @param shadowColor 阴影颜色
*/
fun setImageViewShadow(
shadowWidth: Float = 0f,
shadowColor: Int = R.color.colorB3B3B3
) {
this.shadowWidth = Screen.dp2Px(shadowWidth).toFloat()
this.shadowColor = shadowColor.getColor()
}
override fun onDraw(canvas: Canvas?) {
getRadius()
setDraw(canvas)
super.onDraw(canvas)
}
private fun setDraw(canvas: Canvas?) {
paint.color = strokeColor
paint.isAntiAlias = true
paint.style = Paint.Style.STROKE
paint.strokeWidth = strokeWidth + if (backGroundColor != strokeColor) strokeWidth else 0f
paint.setShadowLayer(shadowWidth, 0f, 0f, shadowColor)
val path = Path()
path.addRoundRect(shadowRectF, radiusArray, Path.Direction.CW)
canvas?.drawPath(path, paint)
canvas?.clipPath(path)
}
/**
* 计算圆角等值
*/
private fun getRadius() {
radiusArray = floatArrayOf(
leftTopRadius, leftTopRadius,
rightTopRadius, rightTopRadius,
rightBottomRadius, rightBottomRadius,
leftBottomRadius, leftBottomRadius
)
shadowRectF = RectF(
shadowWidth + strokeWidth,
shadowWidth + strokeWidth,
width - shadowWidth - strokeWidth,
height - shadowWidth - strokeWidth
)
}
}
使用及效果
// 使用
button.setRadius(strokeWidth = 10f,strokeColor = R.color.redF45265,backgroundColor = R.color.color999999,leftBottomRadius = 10,leftTopRadius = 40,rightBottomRadius = 20,rightTopRadius = 30)
imageView.setRadius(strokeWidth = 2f,strokeColor = R.color.redF45265,backgroundColor = R.color.color999999,leftBottomRadius = 10,leftTopRadius = 40,rightBottomRadius = 100,rightTopRadius = 30)
imageView.setShadow(shadowColor = R.color.color333333,shadowWidth = 5f)
textView.setRadius(strokeWidth = 0.6f, strokeColor = R.color.colorCCCCCC, radius = 3)
textView.setShadow(shadowColor = R.color.redF45265,shadowWidth = 4f)
linearLayout.setRadius(strokeWidth = 0.6f, strokeColor = R.color.colorCCCCCC, radius = 3)
linearLayout.setShadow(shadowColor = R.color.redF45265,shadowWidth = 4f)
不创建大量的drawable,也不用为太多的背景图命名烦恼,基本能满足开发的大部分需求, 欢迎指出问题和改进。