使用DSL方式自定义一个Drawable控件,准备给项目来个全套

本文介绍了如何使用DSL(领域特定语言)来改进Android应用中的自定义Drawable组件,以提高代码可读性、组件扩展性和降低维护成本。通过对比传统的XML方式和自定义View,展示了DSL如何简化背景样式设置,包括圆角、渐变、边框等,并提供了实现示例,包括正常状态、点击状态和水波纹效果。
摘要由CSDN通过智能技术生成

我们使用DSL的方式自定义了一个弹框组件,完全撇弃了以往传统自定义View的命令式方式,采用了声明式构建UI的方式,无论是在代码的可读性上,组件的扩展性上,还有维护成本上,都有了不小的改善,那么在这篇文章中,我们继续用DSL去自定义我们常用的Drawable控件

作者:Coffeeee 链接:https://juejin.cn/post/7206732474112540727

常用的方式

xml文件

我们经常在项目当中遇到需要给控件设置背景样式的场景,比如圆角,渐变等,我们会在项目中新建一个drawable文件,在里面写上对应样式代码,比如这样

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners
        android:radius="10dp" />
    <solid android:color="#000000"/>
</shape>

这样一个黑色背景,圆角是10dp的背景样式就做好了,非常简单,但是我们项目当中不可能只会有一种样式,不同圆角,不同背景色,边框颜色与粗细等等不同的样式组合搭配起来以后,我们项目中的drawable文件的数量是惊人的,造成很大的维护成本

自定义drawable控件

所以介于这种情况,有些项目当中就会加入一个自定义的drawable控件,通过自定义属性,使调用方在xml文件中根据不同需求场景,选择合适的属性去渲染背景,这种方式通常在项目初期可以解决大部分样式需求,可是长久下来依然存在一些不可避免的问题

  1. 属性必须确保唯一性,如果其他自定义控件或者三方控件使用了相同名字的属性,则会编译报错

  2. 一种控件只支持一种布局,如果想要让所有布局都支持自定义drawable样式,必须分别定义各自的子类,然后去代理drawable功能,开发成本较大

  3. 功能无法满足所有场景,维护方需要时常根据需求去扩展控件属性以及功能,造成控件负担过大,产生bug的概率增大

所以为了达到扩展性强,维护成本低,兼容性高的目的,我们现在用DSL的方式去实现一个drawable控件

开始开发

首先为了达到兼容性高的目的,我们的自定义drawable肯定可以设置在任何控件的background属性上,所以首先要做的就是确定个顶层函数,然后参数之一就是接收目标控件

fun rootView(root: View){
    
}

root可以是任何你想设置背景的控件,然后我们就要考虑要往这个控件上设置什么样的样式,先考虑一些常用样式,比如圆角,背景色,渐变色,边框颜色与粗细,马上脑海中我们就知道使用GradientDrawable这个类,用它作为接收者,在它的lambda表达式中将需要的属性渲染出来,我们在rootView中加入第二个参数以及逻辑代码

fun rootView(root: View,
             normalRender: GradientDrawable.() -> Unit){
    val mNormal = GradientDrawable()
    with(mNormal) {
        normalRender()
        root.background = this
    }
}

现在我们可以在上层调用这个函数了,比如需要给root设置个背景为灰色,边框为红色,圆角为10dp的样式,调用方的代码如下

rootView(
    root = bindingView.mainLinear,
    normalRender = {
        cornerRadius = 10f.DP()
        color = ColorStateList.valueOf(getColor(R.color.color_BBBBBB))
        setStroke(2f.DP().toInt(), getColor(R.color.color_FF4081))
    })

结构很清晰,在normalRender里面可以设置任何GradientDrawable支持的属性,运行一下这段代码得到如下效果

d57ea9d24afc23749b46baff1967be9c.jpeg

有了上一步经验,接下去的工作就方便了,我们刚刚只是设置了普通状态下的样式,但是对于一个控件来讲,被点击时候的反馈也是一种样式,有过自定义drawable控件经验的大佬马上清楚了,这里需要用到另一个Drawable的子类--StateListDrawable,做法就是在rootView函数中在增加一个GradientDrawable为接收者的lambda参数,让它作为点击状态的样式,然后在代码块中连通normalRender一起设置在StateListDrawable里面,我们将rootView函数的代码调整一下

fun rootView(root: View,
             normalRender: GradientDrawable.() -> Unit,
             pressedRender: GradientDrawable.() -> Unit = {}){
    val mNormal = GradientDrawable()
    with(mNormal) {
        normalRender()
    }
    val mPressed = GradientDrawable()
    with(mPressed) {
        pressedRender()
    }
    with(StateListDrawable()) {
        addState(
            intArrayOf(
                android.R.attr.state_focused,
                android.R.attr.state_pressed,
                android.R.attr.state_enabled
            ), mPressed
        )
        addState(
            intArrayOf(android.R.attr.state_pressed),
            mPressed
        )
        addState(
            intArrayOf(android.R.attr.state_focused),
            mPressed
        )
        addState(
            intArrayOf(android.R.attr.state_enabled),
            mNormal
        )
        addState(intArrayOf(), mNormal)
        root.isClickable = true
        root.background = this
    }
}

这里别忘了给root的isClickable属性设置为true,不然点击是没有效果的,同时我们给pressedRender设置了个可空的默认值,兼容一些不需要点击效果的控件。现在我们在上层可以给原有的控件添加点击效果了,比如点击效果为背景色为黑白的渐变色,方向从上到下,边框颜色取消,那么调用方代码修改如下

rootView(
    root = bindingView.mainLinear,
    normalRender = {
        cornerRadius = 10f.DP()
        color = ColorStateList.valueOf(getColor(R.color.color_BBBBBB))
        setStroke(2f.DP().toInt(), getColor(R.color.color_FF4081))
    },
    pressedRender = {
        cornerRadius = 10f.DP()
        colors = intArrayOf(getColor(R.color.black), getColor(R.color.white))
        orientation = GradientDrawable.Orientation.TOP_BOTTOM
    })

哪个代码块做什么事情一目了然,如果不需要点击事件只需要去掉pressedRender就好了,我们运行一遍得到效果如下

a489f311447beb885402792b88e83e48.jpeg

点击效果设置完成了,我们肯定还需要另一种效果,也就是水波纹效果,水波纹用到了RippleDrawable这个类,这里我们需要给rootView增加一个开关,需不需要用水波纹,打开就是使用水波纹效果,关闭则使用我们设置的点击效果,同时增加一个水波纹颜色的参数,rootView函数调整如下

inline fun rootView(
    root: View,
    normalRender: GradientDrawable.() -> Unit,
    pressedRender: GradientDrawable.() -> Unit = {},
    ripple: Boolean = false,
    rippleColor: Int = 0
) {
    val mNormal = GradientDrawable()
    with(mNormal) {
        normalRender()
    }
    val mPressed = GradientDrawable()
    with(mPressed) {
        pressedRender()
    }
    if (ripple) {
        val rippleDrawable = RippleDrawable(
            ColorStateList(
                arrayOf(
                    intArrayOf(android.R.attr.state_pressed),
                    intArrayOf(android.R.attr.state_focused),
                    intArrayOf(android.R.attr.state_activated)
                ),
                intArrayOf(
                    rippleColor,
                    rippleColor,
                    rippleColor
                )
            ), mNormal, ShapeDrawable()
        )
        root.isClickable = true
        root.background = rippleDrawable
    } else {
        with(StateListDrawable()) {
            addState(
                intArrayOf(
                    android.R.attr.state_focused,
                    android.R.attr.state_pressed,
                    android.R.attr.state_enabled
                ), mPressed
            )
            addState(
                intArrayOf(android.R.attr.state_pressed),
                mPressed
            )
            addState(
                intArrayOf(android.R.attr.state_focused),
                mPressed
            )
            addState(
                intArrayOf(android.R.attr.state_enabled),
                mNormal
            )
            addState(intArrayOf(), mNormal)
            root.isClickable = true
            root.background = this
        }
    }
}

同样我们给开关与水波纹颜色设置了默认值,兼容那些不需要水波纹效果的场景,现在我们在调用方那里添加一个水波纹效果,水波纹颜色为黄色,代码如下

rootView(
    root = bindingView.mainLinear,
    normalRender = {
        cornerRadius = 10f.DP()
        color = ColorStateList.valueOf(getColor(R.color.color_BBBBBB))
        setStroke(2f.DP().toInt(), getColor(R.color.color_FF4081))
    },
    pressedRender = {
        cornerRadius = 10f.DP()
        colors = intArrayOf(getColor(R.color.black), getColor(R.color.white))
        orientation = GradientDrawable.Orientation.TOP_BOTTOM
    }, ripple = true, rippleColor = getColor(R.color.color_f7c653)
)

代码运行效果如下

423068dcdd1dff46b3df8e334bf2f1a6.jpeg

总结

一个简单实用的drawable控件就完成了,没有去继承任何View,但可以设置在任何View上面,也不用去attrs.xml文件里面去定义属性,样式的设置完全依赖于系统的api,降低了bug产生的概率,我们以后在开发过程当中,一些简单的自定义控件就都可以使用DSL的方式来实现。

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

9e6759c429a65713131d9b0b362c9e40.jpeg

70dfa3e4dd568359ca46617988bc8e79.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值