Kotlin 风格,应该这样写drawable !

前言

通常我们在res/drawable下面自定义shape和selector来满足一些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/DrawableDsl

人工智能
上一篇:域名不仅仅是一个简单的网站。对于有长远眼光的公司来说,在运营网站之前确定一个优秀的域名对有长远眼光的公司来说是非常重要的。这对今后的市场营销、产品营销和企业品牌建设都具有十分重要的意义。优秀的域名是企业在市场竞争中获得持久优势的利器。
下一篇:3、不明先知,根据相关征兆预测可能发生的事件,以便提前做好准备,赶紧注册相关域名。;不差钱域名;buchaqian抢先注册,就是这种敏感类型。预言是最敏感的状态。其次,你应该有眼力。所谓眼力,就是善于从社会上时不时出现的各种热点事件中获取与事件相关的域名资源。眼力的前提是对域名领域的熟悉和丰富的知识。