在上一篇运用DSL的方法自界说了一个弹框,代码遽然变的有那么一点点美观,咱们运用DSL的方法自界说了一个弹框组件,彻底抛弃了以往传统自界说View的指令式方法,采用了声明式构建UI的方法,无论是在代码的可读性上,组件的扩展性上,还有保护本钱上,都有了不小的改善,那么在这篇文章中,咱们继续用DSL去自界说咱们常用的Drawable控件

常用的方法

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支撑的特点,运行一下这段代码得到如下作用

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

有了上一步经历,接下去的作业就方便了,咱们刚刚仅仅设置了普通状态下的款式,可是对于一个控件来讲,被点击时候的反馈也是一种款式,有过自界说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就好了,咱们运行一遍得到作用如下

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

点击作用设置完成了,咱们肯定还需求另一种作用,也便是水波纹作用,水波纹用到了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)
)

代码运行作用如下

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

总结

一个简略有用的drawable控件就完成了,没有去承继任何View,但能够设置在任何View上面,也不用去attrs.xml文件里边去界说特点,款式的设置彻底依赖于体系的api,降低了bug发生的概率,咱们今后在开发过程傍边,一些简略的自界说控件就都能够运用DSL的方法来完成。