Kotlin style, it should be written like drawable!

preface

Generally, we customize shape s and selector s under res/drawable to meet some UI designs. However, because the final conversion of xml to drawable needs to be created through IO or reflection, there will be some performance loss. In addition, with the increase and modularization of the project, many common styles can not be reused quickly, and they need reasonable project resource management specifications to be implemented. These side effects can be reduced to a certain extent by directly creating these drawable through code. This article introduces the implementation of common drawable with the concise syntax features of kotlin DSL.

Code corresponding effect preview

Integration and use

In project level build Add warehouse Jitpack to gradle file:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Add dependency

dependencies {  
 implementation 'com.github.forJrking:DrawableDsl:0.0.3'
}

Discard xml creation method example (see demo for others)

//The usage of infix is used to remove parentheses, which is more concise and will be described in detail later
image src shapeDrawable {
    //Specify shape style
    shape(ShapeBuilder.Shape.RECTANGLE)
    //Fillet, supporting 4 corners to be set separately
    corner(20f)
    //solid color
    solid("#ABE2E3")
    //stroke} color, border dp, dotted line setting
    stroke(R.color.white, 2f, 5f, 8f)
}
//Button click style
btn.background = selectorDrawable {
    //Default style
    normal = shapeDrawable {
        corner(20f)
        gradient(90, R.color.F97794, R.color.C623AA2)
    }
    //Click effect
    pressed = shapeDrawable {
        corner(20f)
        solid("#84232323")
    }
}

Realization idea

How to convert xml to drawable

xml becomes drawable through Android graphics. drawable. The drawableinflator class is used to create the IO parsing tag, and then set the properties by parsing the tag:

//Label creation
private Drawable inflateFromTag(@NonNull String name) {
    switch (name) {
        case "selector":
            return new StateListDrawable();
        case "level-list":
            return new LevelListDrawable();
        case "layer-list":
            return new LayerDrawable();
        ....
        case "color":
            return new ColorDrawable();
        case "shape":
            return new GradientDrawable();
        case "vector":
            return new VectorDrawable();
        ...
    }
}
//Reflection creation
private Drawable inflateFromClass(@NonNull String className) {
    try {
        Constructor<? extends Drawable> constructor;
        synchronized (CONSTRUCTOR_MAP) {
            constructor = CONSTRUCTOR_MAP.get(className);
            if (constructor == null) {
                final Class<? extends Drawable> clazz = mClassLoader.loadClass(className).asSubclass(Drawable.class);
                constructor = clazz.getConstructor();
                CONSTRUCTOR_MAP.put(className, constructor);
            }
        }
        return constructor.newInstance();
    } catch (NoSuchMethodException e) {
    ...
}

code implementation

Since creating a shape requires setting various properties to build it, which is more in line with the build design mode, let's first encapsulate the shapeBuilder in the build mode. Although the code is more than using apply {} directly, it can make pure java projects very comfortable to use. For other implementations, please check the source Code:

class ShapeBuilder : DrawableBuilder {
    private var mRadius = 0f
    private var mWidth = 0f
    private var mHeight = 0f
    ...
    private var mShape = GradientDrawable.RECTANGLE
    private var mSolidColor = 0

    /**Fillet the four corners separately*/
    fun corner(leftTop: Float,rightTop: Float,leftBottom: Float,rightBottom: Float): ShapeBuilder {
        ....if(dp)dp2px(leftTop) else leftTop
        return this
    }

    fun solid(@ColorRes colorId: Int): ShapeBuilder {
        mSolidColor = ContextCompat.getColor(context, colorId)
        return this
    }
    //Omit other parameter setting methods. View the source code in detail
    override fun build(): Drawable {
        val gradientDrawable = GradientDrawable()
        gradientDrawable = GradientDrawable()
        gradientDrawable.setColor(mSolidColor)
        gradientDrawable.shape = mShape
        ....Other parameter settings
        return gradientDrawable
    }    
}
Convert build mode to dsl

Theoretically, all build modes can be easily converted to dsl:

inline fun shapeDrawable(builder: ShapeBuilder.() -> Unit): Drawable {
    return ShapeBuilder().also(builder).build()
}
//Method of use
val drawable = shapeDrawable{
    ...
}

Note: for dsl usage, see Juejin Cn / post / 695318... dsl section

Function de bracket

The dsl writing method has been realized through the above encapsulation. Generally, setBackground can be simplified by setter. However, I found that some api designs also need parentheses, so it is not kotlin:

//Easy to read
iv1.background = shapeDrawable {
    shape(ShapeBuilder.Shape.RECTANGLE)
    solid("#ABE2E3")
}
//Too many parentheses look uncomfortable
iv2.setImageDrawable(shapeDrawable {
    solid("#84232323")
})

How to remove the brackets? 🈶 There are two methods: infix function (infix expression) and property setter

infix function features and specifications:

  • Kotlin allows functions to be called without parentheses and periods
  • There must be only one parameter
  • Must be a member function or an extension function
  • Variable parameters and parameters with default values are not supported
/**Add an extended infix} function for all imageviews to remove parentheses*/
infix fun ImageView.src(drawable: Drawable?) {
    this.setImageDrawable(drawable)
}
//Use the following
iv2 src shapeDrawable {
    shape(ShapeBuilder.Shape.OVAL)
    solid("#E3ABC2")
}

Of course, the code is for reading. Personally, I think it will be more difficult to read if we use a lot of infix functions, so it is suggested that the function naming must be able to directly hit the function function, and the function function is simple and single.

The property setter method mainly uses kotlin, which can simplify the setter to variable =, and remove parentheses:

/**Extended variable*/
var ImageView.src: Drawable
    get() = drawable
    set(value) {
        this.setImageDrawable(value)
    }
//Use the following
iv2.src = shapeDrawable {
    shape(ShapeBuilder.Shape.OVAL)
    solid("#E3ABC2")
}
Advantages and disadvantages

advantage:

  • Direct code creation can improve performance compared with xml
  • Compared with build mode and calling method setting, dsl mode is more concise and conforms to kotlin style
  • These codes can be reused through appropriate code management, which is more convenient than xml management

Disadvantages:

  • There is no preview function of as, only through on-board observation
  • The api does not cover all drawable attributes (such as shape = ring, etc.)

Post language

The basic usage of DrawableDsl has been introduced above. Welcome to use it. Welcome to mention Issues. Remember to give it to star. Github link: https://github.com/forJrking/...

end of document

Your favorite collection is my greatest encouragement!
Welcome to follow me, share Android dry goods and exchange Android technology.
If you have any opinions on the article or any technical problems, please leave a message in the comment area for discussion!

Keywords: Android Back-end

Added by Mr. Tech on Fri, 17 Dec 2021 08:26:47 +0200