干掉shape,手动构建GradientDrwable

"该博客介绍了一种使用Kotlin封装的方法,通过定义密封类和扩展函数,避免XML文件过多和解析耗时,实现TextView、Button等组件的圆角、渐变、边框等效果。通过"+"运算符重载实现不同样式操作的组合,并提供了通用方法简化创建过程,使得代码更简洁易读。"
摘要由CSDN通过智能技术生成

日常开发中,实现比如TextViewButton等组件的圆角、渐变、边框一般都是定义个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")

331b6aef81957c52033ec7d212a609ac.png接下来,就开始一步步的讲解这种写法是如何封装的

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具体操作实现的SolidCorner等等

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

关注我获取更多知识或者投稿

402bac6c5e83953f1a31d2cc3339c2a2.png

b26a6401ac19aa1d9a04269cbcb6f518.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值