Kill shape and build GradientDrwable manually

Get into the habit of writing together! This is the second day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

In daily development, the rounded corners, gradients, and borders of components such as TextView, Buttonare generally defined by an xml file, and then implemented using shapes, such as:

<?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>
复制代码

This notation has two disadvantages:

  • Causes too many xml files in the resource path, which is not easy to manage
  • XML file parsing involves io, which increases parsing time

There are also many solutions on the Internet. The core is generally constructed manually through code GradientDrawable. This article is no exception. It mainly uses Kotlin密封类、扩展函数、+运算符重载、单链表头插法such as to achieve the following effects:

mBinding.goMeetingBtn.shape = corner(17) +
        stroke(5, "#ff0000") +
        gradient(GradientDrawable.Orientation.RIGHT_LEFT, "#00ff00", "#0000ff")
复制代码

0ec1a95445aaf015a29798de591a6f4.jpgNext, I will start to explain step by step how this writing method is encapsulated

GradientDrawable1. Four basic operations defined by the sealed class

The shape in the xml is ultimately converted into GradientDrawable. Usually, we use the shape mainly to realize the four operations of View background, rounded corners, gradients, and borders. We define these four operations into four states using the sealed class:

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()
}
复制代码

Among them:
Solid: background color fill, the color to be filled is specified in the construction method
Corner: background rounded corners, you can specify the respective angles of the four corners separately, or you can specify together
Stroke: background border, in addition to specifying the thickness and color of the border, you can also specify the border The interval gap and background shape of the upper dotted line
GradientState: background gradient filling, the gradient direction, start color and end color need to be passed in the construction method

2. The four basic operations are superimposed through the "+" sign

The four basic operations defined above can be combined with each other to set the View background, a very convenient way to achieve the superposition of operations by adding "+".
We can implement operator overloading in kotlin. The overloaded function corresponding to "+" is plus:

sealed class ShapeDrawable {
    operator fun plus(shape: ShapeDrawable): ShapeDrawable {
        return shape
    }
}
复制代码

Solid() + Corner()In this way , the effect of combining the four basic operations such as

3. "+" how to save the four basic operations of overlay

This is mainly to use the singly linked list idea to combine ShapeDrawablethe specific operation implementation classes into a chain

  • Define a next attribute in the sealed class ShapeDrawable, pointing to the next ShapeDrawablespecific operation implementation Solid, Corneretc.
sealed class ShapeDrawable {
    var next: ShapeDrawable? = null
}
复制代码
  • Implementing specific operations through the "+" plusoperator overloading function to ShapeDrawableachieve class insertion
sealed class ShapeDrawable {
    var next: ShapeDrawable? = null

    operator fun plus(shape: ShapeDrawable): ShapeDrawable {
        shape.next = this
        return shape
    }
}
复制代码

The above needs to be implemented using the head insertion method of the linked list

4. ShapeDrawableDefine GradientDrawableabstract methods for operations on

Defining operations boxon method encapsulation pairsGradientDrawable

sealed class ShapeDrawable {
    var next: ShapeDrawable? = null

    abstract fun box(drawable: GradientDrawable?): GradientDrawable
}
复制代码

Concrete implementations are implemented by ShapeDrawableconcrete subclasses Solid、Stroke、GradientState、Cornerof

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. Define the extension properties of View to shapeachieve background settings

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
    }
复制代码

Through the above steps, we can achieve the following effects:

mBinding.goMeetingBtn.shape = ShapeDrawable.Corner(17) +
        ShapeDrawable.Stroke(5, "#ff0000") +
        ShapeDrawable.GradientState(GradientDrawable.Orientation.RIGHT_LEFT, "#00ff00", "#0000ff")
复制代码

But I found that setting the background operation needs to be called every time ShapeDrawable.Corner(), ShapeDrawable.Strokeetc. too much trouble

6. Define general methods to quickly realize ShapeDrawable.xxxthe creation of concrete subclasses

fun solid(color: Any): ShapeDrawable.Solid {
    return ShapeDrawable.Solid(color)
}

fun corner(radius: Int): ShapeDrawable.Corner {
    return ShapeDrawable.Corner(radius)
}
复制代码

Solid、CornerOnly the creation method of the class is listed here , Stroke、GradientStatesimilar.

Finally, we can implement the code shown at the beginning of the article:

mBinding.goMeetingBtn.shape = corner(17) +
        stroke(5, "#ff0000") +
        gradient(GradientDrawable.Orientation.RIGHT_LEFT, "#00ff00", "#0000ff")
复制代码

7. Complete Code Reference

github: Manually build GradientDrawable instead of xml shape

Guess you like

Origin juejin.im/post/7082321786720747527