乐趣区

关于android:Kotlin-风格应该这样写drawable

前言

通常咱们在 res/drawable 上面自定义 shapeselector来满足一些 UI 的设计,然而因为 xml 最终转换为 drawable 须要通过 IO 或反射创立,会有一些性能损耗,另外随着我的项目的增大和模块化等,很多通用的款式并不能疾速复用,须要正当的我的项目资源管理标准能力施行。那么通过代码间接创立这些drawable,能够在肯定水平上升高这些副作用。本篇介绍用 kotlin DSL 简洁的语法个性来实现常见的drawable

代码对应成果预览

集成和应用

在我的项目级的 build.gradle 文件种增加仓库 Jitpack:

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

增加依赖

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

摈弃 xml 创立形式示例(其余参见 demo)

// infix 用法用于去掉括号更加简洁,具体前面阐明
image src shapeDrawable {
    // 指定 shape 款式
    shape(ShapeBuilder.Shape.RECTANGLE)
    // 圆角,反对 4 个角独自设置
    corner(20f)
    //solid 色彩
    solid("#ABE2E3")
    //stroke 色彩,边框 dp,虚线设置
    stroke(R.color.white, 2f, 5f, 8f)
}
// 按钮点击款式
btn.background = selectorDrawable {
    // 默认款式
    normal = shapeDrawable {corner(20f)
        gradient(90, R.color.F97794, R.color.C623AA2)
    }
    // 点击成果
    pressed = shapeDrawable {corner(20f)
        solid("#84232323")
    }
}

实现思路

xml 如何转换成 drawable

xml变成 drawable,通过android.graphics.drawable.DrawableInflater 这个类来 IO 解析标签创立,而后通过解析标签再设置属性:

// 标签创立
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();
        ...
    }
}
// 反射创立
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) {...}

代码实现

因为创立 shape 等须要设置各种属性来构建,比拟合乎 build 设计模式,那咱们首先封装 build 模式的 shapeBuilder,这样做尽管代码比起间接应用apply{} 要多,然而能够让纯 java 我的项目用起来很难受,其余实现请查看源码:

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

    /** 别离设置四个角的圆角 */
    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
    }
    // 省略其余参数设置办法 具体代码查看源码
    override fun build(): Drawable {val gradientDrawable = GradientDrawable()
        gradientDrawable = GradientDrawable()
        gradientDrawable.setColor(mSolidColor)
        gradientDrawable.shape = mShape
        .... 其余参数设置
        return gradientDrawable
    }    
}
把 build 模式转换为 dsl

实践上所有的 build 模式都能够轻松转换为 dsl 写法:

inline fun shapeDrawable(builder: ShapeBuilder.() -> Unit): Drawable {return ShapeBuilder().also(builder).build()}
// 应用办法 
val drawable = shapeDrawable{...}

备注:dsl 用法参见 juejin.cn/post/695318… 中 dsl 大节

函数去括号

通过下面封装曾经实现了 dsl 的写法,通常 setBackground 能够通过 setter 简化,然而我发现因为有些 api 设计还须要加括号,这样不太 kotlin:

// 容易浏览
iv1.background = shapeDrawable {shape(ShapeBuilder.Shape.RECTANGLE)
    solid("#ABE2E3")
}
// 多了括号看起来不难受
iv2.setImageDrawable(shapeDrawable {solid("#84232323")
})

怎么去掉括号呢?🈶2 种形式 infix 函数 (中断表白) 和property setter

infix 函数特点和标准:

  • Kotlin 容许在不应用括号和点号的状况下调用函数
  • 必须只有一个参数
  • 必须是成员函数或扩大函数
  • 不反对可变参数和带默认值参数
/** 为所有 ImageView 增加扩大 infix 函数 来去掉括号 */
infix fun ImageView.src(drawable: Drawable?) {this.setImageDrawable(drawable)
}
// 应用如下
iv2 src shapeDrawable {shape(ShapeBuilder.Shape.OVAL)
    solid("#E3ABC2")
}

当然了代码是用来浏览的。集体认为如果咱们大量应用 infix 函数,浏览艰难会大大增加,所以倡议函数命名必须能够直击函数性能,而且函数性能简略且繁多。

property setter 形式,次要应用 kotlin 能够简化 setter 为 变量 = 来去括号:

/** 扩大变量 */
var ImageView.src: Drawable
    get() = drawable
    set(value) {this.setImageDrawable(value)
    }
// 应用如下   
iv2.src = shapeDrawable {shape(ShapeBuilder.Shape.OVAL)
    solid("#E3ABC2")
}
优缺点

长处:

  • 代码间接创立比起 xml 形式能够晋升性能
  • dsl 形式比起 build 模式和调用办法设置更加简洁合乎 kotlin 格调
  • 通过适合的代码治理能够复用这些代码,比 xml 治理不便

毛病:

  • 没有 as 的预览性能,只有通过上机观测
  • api 还没有笼罩所有 drawable 属性(例如 shape = ring 等)

后语

下面把的 DrawableDsl 根底用法介绍完了,欢送大家应用,欢送提 Issues,记得给个 star 哦。Github 链接:https://github.com/forJrking/…

文末

您的点赞珍藏就是对我最大的激励!
欢送关注我,分享 Android 干货,交换 Android 技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

退出移动版