日常开发中,实现比如
TextView
、Button
等组件的圆角、渐变、边框一般都是定义个xml文件,然后使用shape实现,比如:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="7dp" />
<solid android:color="#2196F3" />
<stroke android:width="4dp" android:color="#ff0000"/>
</shape>
这种写法有两个不好的地方:
造成资源路径下xml文件数量过多,不好管理
xml文件解析涉及到了io,增加了解析耗时
现在网上也有很多的方案,核心一般都是通过代码手动构建GradientDrawable
,本文也不例外,主要利用Kotlin密封类、扩展函数、+运算符重载、单链表头插法
等实现下面这种效果:
mBinding.goMeetingBtn.shape = corner(17) +
stroke(5, "#ff0000") +
gradient(GradientDrawable.Orientation.RIGHT_LEFT, "#00ff00", "#0000ff")
接下来,就开始一步步的讲解这种写法是如何封装的
1.密封类定义GradientDrawable
四大基本操作
xml中shape最终是要转换成GradientDrawable
,平常我们使用shape主要就是为了实现View背景、圆角、渐变、边框四种操作,我们就将这四种操作利用密封类定义成四种状态:
sealed class ShapeDrawable {
class Solid(val color: Any) : ShapeDrawable()
class Corner : ShapeDrawable {
var radiusArray: FloatArray? = null
var radius: Float = 0.0f
constructor(radius: Int) : super() {
this.radius = radius.toFloat()
}
constructor(
topLeftRadius: Int,
topRightRadius: Int,
bottomRightRidus: Int,
bottomLeftRidus: Int
)
data class Stroke(
val strokeWidth: Int,
val dashColor: Any,
val dashWidth: Int = 0,
val dashGap: Int = 0,
val shapeType: Int = GradientDrawable.RECTANGLE
) : ShapeDrawable()
data class GradientState(
val orientation: GradientDrawable.Orientation,
val startColor: Any,
val endColor: Any
) : ShapeDrawable()
}
其中:Solid
:背景颜色填充,构造方法中指定要填充的颜色Corner
:背景圆角,可以分别指定四个角各自的角度,也可以共同指定Stroke
:背景边框,除了需要指定边框粗细、颜色,还可以指定边框上虚线的间隔gap、背景形状GradientState
:背景渐变填充,构造方法中需要传入渐变方向、起始颜色和结束颜色
2.通过"+"号实现四大基本操作叠加
上面定义的四大基本操作是可以互相进行组合来设置View背景,一种非常方便的方式通过相加"+"实现操作的叠加。
我们可以kotlin的运算符重载实现,"+"对应的重载函数为plus
:
sealed class ShapeDrawable {
operator fun plus(shape: ShapeDrawable): ShapeDrawable {
return shape
}
}
这样就可以实现诸如Solid() + Corner()
等四大基本操作互相组合的效果
3."+"如何保存叠加的四大基本操作
这个主要是利用单链表思想,将ShapeDrawable
的具体操作实现类组合成一条链
密封类
ShapeDrawable
中定义个next属性,指向下一个ShapeDrawable
具体操作实现的Solid
、Corner
等等
sealed class ShapeDrawable {
var next: ShapeDrawable? = null
}
通过"+"的
plus
运算符重载函数实现ShapeDrawable
具体操作实现类的插入
sealed class ShapeDrawable {
var next: ShapeDrawable? = null
operator fun plus(shape: ShapeDrawable): ShapeDrawable {
shape.next = this
return shape
}
}
上面需要使用链表的头插法实现
4.ShapeDrawable
定义对GradientDrawable
的操作的抽象方法
定义box
方法封装对GradientDrawable
的操作
sealed class ShapeDrawable {
var next: ShapeDrawable? = null
abstract fun box(drawable: GradientDrawable?): GradientDrawable
}
具体的实现由ShapeDrawable
的具体子类Solid、Stroke、GradientState、Corner
实现
sealed class ShapeDrawable {
abstract fun box(drawable: GradientDrawable?): GradientDrawable
class Solid: ShapeDrawable() {
override fun box(drawable: GradientDrawable?): GradientDrawable {
drawable!!.setColor(color.color)
return drawable
}
}
class Corner: ShapeDrawable {
var radiusArray: FloatArray? = null
var radius: Float = 0.0f
override fun box(drawable: GradientDrawable?): GradientDrawable {
if (radiusArray == null) {
drawable!!.cornerRadius = radius
} else {
drawable!!.cornerRadii = radiusArray
}
return drawable
}
}
data class Stroke: ShapeDrawable() {
override fun box(drawable: GradientDrawable?): GradientDrawable {
drawable!!.apply {
setStroke(
strokeWidth.dp.toInt(),
dashColor.color,
dashWidth.dp,
dashGap.dp
)
shape = shapeType
}
return drawable
}
}
data class GradientState: ShapeDrawable() {
override fun box(drawable: GradientDrawable?): GradientDrawable {
//因为要构造方法传入,使用时这个需要放在第一位
return GradientDrawable(
orientation, intArrayOf(
startColor.color,
endColor.color
)
)
}
}
}
5.定义View的扩展属性shape
实现背景设置
var View.shape: ShapeDrawable
get() = ShapeDrawable.Empty()
set(value) {
var s: ShapeDrawable? = value
val list = mutableListOf<ShapeDrawable>()
var drawable: GradientDrawable? = null
while (s != null) {
if (s is ShapeDrawable.GradientState) {
drawable = s.box(null)
} else {
list.add(s)
}
s = s.next
}
if (drawable == null) {
drawable = GradientDrawable()
}
list.forEach {
it.box(drawable)
}
background = drawable
}
通过以上几步,我们就可以实现如下效果:
mBinding.goMeetingBtn.shape = ShapeDrawable.Corner(17) +
ShapeDrawable.Stroke(5, "#ff0000") +
ShapeDrawable.GradientState(GradientDrawable.Orientation.RIGHT_LEFT, "#00ff00", "#0000ff")
但是发现,设置背景操作每次都需要调用ShapeDrawable.Corner()
、ShapeDrawable.Stroke
等等太过于麻烦
6.定义通用方法快速实现ShapeDrawable.xxx
具体子类的创建
fun solid(color: Any): ShapeDrawable.Solid {
return ShapeDrawable.Solid(color)
}
fun corner(radius: Int): ShapeDrawable.Corner {
return ShapeDrawable.Corner(radius)
}
这里只列出了Solid、Corner
类的创建方法,Stroke、GradientState
类似。
最终我们就可以实现文章开头一开始展示的代码:
mBinding.goMeetingBtn.shape = corner(17) +
stroke(5, "#ff0000") +
gradient(GradientDrawable.Orientation.RIGHT_LEFT, "#00ff00", "#0000ff")
7.完整的代码参考
github:手动构建GradientDrawable替代xml的shape
https://github.com/emptyNesterAfter90/emptyNesterAfter90.github.io/blob/master/DrawableExtension.kt
作者:长安皈故里
链接:https://juejin.cn/post/7082321786720747527
关注我获取更多知识或者投稿