Kotlin should write the series like this

SharedPreferences should be written like this with Kotlin

Glide with Kotlin should be encapsulated like this (1)

Glide with Kotlin should be encapsulated like this (2)

preface

In general, we customize shapes and selectors under res/drawable to meet some UI design requirements. However, because the final transformation from XML to drawable needs to be created through IO or reflection, there will be some performance costs. In addition, with the increase of project size and modularity, many common styles cannot be reused quickly. Need reasonable project resource management standard to implement. By creating these drawables directly in your code, you can reduce these side effects to some extent. This article introduces the use of the concise syntax features of kotlin DSLS to implement common drawables.

The code corresponds to the effect preview

Integration and use

  1. Add the repository Jitpack to the build.gradle file at the project level:
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io'}}}Copy the code
  1. Add the dependent
dependencies {		
	implementation 'com. Making. ForJrking: DrawableDsl: 0.0.3}'Copy the code
  1. Examples of discard XML creation (see Demo for others)
// The infix usage is more concise when the parentheses are removed
image src shapeDrawable {
    // Specify the shape style
    shape(ShapeBuilder.Shape.RECTANGLE)
    // Rounded corners. Supports four separate corners
    corner(20f)
    / / solid color
    solid("#ABE2E3")
    // Stroke color, border dp, dashed line Settings
    stroke(R.color.white, 2f.5f.8f)}// Click the style button
btn.background = selectorDrawable {
    // Default style
    normal = shapeDrawable {
        corner(20f)
        gradient(90, R.color.F97794, R.color.C623AA2)
    }
    // Click the effect
    pressed = shapeDrawable {
        corner(20f)
        solid("# 84232323")}}Copy the code

Implementation approach

How does XML become drawable

XML becomes drawable, through the android. The graphics. The drawable. DrawableInflater this class to IO parsing TAB to create, and then by parsing the tag set properties:

// 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 newLayerDrawable(); .case "color":
            return new ColorDrawable();
        case "shape":
            return new GradientDrawable();
        case "vector":
            return newVectorDrawable(); . }}// Create reflection
private Drawable inflateFromClass(@NonNull String className) {
    try {
        Constructor<? extends Drawable> constructor;
        synchronized (CONSTRUCTOR_MAP) {
            constructor = CONSTRUCTOR_MAP.get(className);
            if (constructor == null) {
                finalClass<? extends Drawable> clazz = mClassLoader.loadClass(className).asSubclass(Drawable.class); constructor = clazz.getConstructor(); CONSTRUCTOR_MAP.put(className, constructor); }}return constructor.newInstance();
    } catch (NoSuchMethodException e) {
    ...
}
Copy the code

Code implementation

Because create shape and so on need to set various properties to build, more in line with build design pattern, that we first encapsulate build mode shapeBuilder, although this code is more than direct use apply{}, but can make pure Java projects to use very comfortable, other implementation please see 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

    /** Set fillet */ for each of the four corners
    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 Settings method detailed code view source code
    override fun build(a): Drawable {
        valgradientDrawable = GradientDrawable() gradientDrawable = GradientDrawable() gradientDrawable.setColor(mSolidColor) gradientDrawable.shape = mShape .... Other Parameter Settingsreturn gradientDrawable
    }    
}
Copy the code

Convert build mode to DSL

In theory, all build modes can be easily converted to DSL writing:

inline fun shapeDrawable(builder: ShapeBuilder. () - >Unit): Drawable {
    return ShapeBuilder().also(builder).build()
}
// Usage
val drawable = shapeDrawable{
    ...
}
Copy the code

Note: For DSL usage, see juejin.cn/post/695318… A DSL in section

Function unparentheses

The above wrapper has already implemented the writing of the DSL. Usually setBackground can be simplified by setters, but I find it a little kotlin because some API designs require parentheses:

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

How do I get rid of the parentheses? 🈶 Two ways to infix function (infix expression) and property setter

  1. Infix functionCharacteristics and specifications:
  • Kotlin is allowed in non-useparenthesesAnd the dot
  • 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 the extended infix function to all imageViews to remove the parentheses */
infix fun ImageView.src(drawable: Drawable?). {
    this.setImageDrawable(drawable)
}
// Use the following
iv2 src shapeDrawable {
    shape(ShapeBuilder.Shape.OVAL)
    solid("#E3ABC2")}Copy the code

Of course the code is meant to be read. Personally, I think that if we use a lot of infix functions, the reading difficulty will be greatly increased, so I suggest that the function name should be able to directly hit the function function, and the function function function is simple and single.

  1. property setterThe way, mainly using Kotlin can be simplifiedsetterforVariable =Come and go parentheses:
/** extension variable */ var imageview.src: Drawable get() = Drawable set(value) {this.setimagedRawable (value)} iv2.src = shapeDrawable { shape(ShapeBuilder.Shape.OVAL) solid("#E3ABC2") }Copy the code

Thank you @ ding Awe-Rin guidance, welcome to discuss learning together, common progress.

The advantages and disadvantages

Advantages:

  • Code created directly can improve performance over XML
  • The DSL approach is much cleaner and kotlin style than the build pattern and call method Settings
  • This code can be reused with proper code management, which is easier than XML management

Disadvantages:

  • There is no preview function of AS, only through the observation of the computer
  • The API has not yet overridden all drawable properties (e.g. shape = ring, etc.)

After the language

DrawableDSL.drawableDSL.drawableDSL.drawableDSL.drawableDSL Github: github.com/forJrking/D…