服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - 编程技术 - Kotlin 风格,应该这样写drawable !

Kotlin 风格,应该这样写drawable !

2021-08-19 01:00掘金forJrking 编程技术

通常我们在res/drawable下面自定义shape和selector来满足一些UI的设计,但是由于xml最终转换为drawable需要经过IO或反射创建,会有一些性能损耗,另外随着项目的增大和模块化等,很多通用的样式并不能快速复用,需要合理的项目资源管

前言

通常我们在res/drawable下面自定义shape和selector来满足一些UI的设计,但是由于xml最终转换为drawable需要经过IO或反射创建,会有一些性能损耗,另外随着项目的增大和模块化等,很多通用的样式并不能快速复用,需要合理的项目资源管理规范才能实施。那么通过代码直接创建这些drawable,可以在一定程度上降低这些副作用。本篇介绍用kotlin DSL简洁的语法特性来实现常见的drawable。

Kotlin 风格,应该这样写drawable !

代码对应效果预览

Kotlin 风格,应该这样写drawable !

Kotlin 风格,应该这样写drawable !

Kotlin 风格,应该这样写drawable !

Kotlin 风格,应该这样写drawable !

集成和使用

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

  1. allprojects { 
  2.     repositories { 
  3.         ... 
  4.         maven { url 'https://jitpack.io' } 
  5.     } 

添加依赖

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

抛弃xml创建方式示例(其他参见demo)

  1. // infix用法用于去掉括号更加简洁,详细后面说明 
  2. image src shapeDrawable { 
  3.     //指定shape样式 
  4.     shape(ShapeBuilder.Shape.RECTANGLE) 
  5.     //圆角,支持4个角单独设置 
  6.     corner(20f) 
  7.     //solid 颜色 
  8.     solid("#ABE2E3"
  9.     //stroke 颜色,边框dp,虚线设置 
  10.     stroke(R.color.white, 2f, 5f, 8f) 
  11. //按钮点击样式 
  12. btn.background = selectorDrawable { 
  13.     //默认样式 
  14.     normal = shapeDrawable { 
  15.         corner(20f) 
  16.         gradient(90, R.color.F97794, R.color.C623AA2) 
  17.     } 
  18.     //点击效果 
  19.     pressed = shapeDrawable { 
  20.         corner(20f) 
  21.         solid("#84232323"
  22.     } 

实现思路

xml如何转换成drawable

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

  1. //标签创建 
  2. private Drawable inflateFromTag(@NonNull String name) { 
  3.     switch (name) { 
  4.         case "selector"
  5.             return new StateListDrawable(); 
  6.         case "level-list"
  7.             return new LevelListDrawable(); 
  8.         case "layer-list"
  9.             return new LayerDrawable(); 
  10.         .... 
  11.         case "color"
  12.             return new ColorDrawable(); 
  13.         case "shape"
  14.             return new GradientDrawable(); 
  15.         case "vector"
  16.             return new VectorDrawable(); 
  17.         ... 
  18.     } 
  19. //反射创建 
  20. private Drawable inflateFromClass(@NonNull String className) { 
  21.     try { 
  22.         Constructor<? extends Drawable> constructor; 
  23.         synchronized (CONSTRUCTOR_MAP) { 
  24.             constructor = CONSTRUCTOR_MAP.get(className); 
  25.             if (constructor == null) { 
  26.                 final Class<? extends Drawable> clazz = mClassLoader.loadClass(className).asSubclass(Drawable.class); 
  27.                 constructor = clazz.getConstructor(); 
  28.                 CONSTRUCTOR_MAP.put(className, constructor); 
  29.             } 
  30.         } 
  31.         return constructor.newInstance(); 
  32.     } catch (NoSuchMethodException e) { 
  33.     ... 

代码实现

由于创建shape等需要设置各种属性来构建,比较符合build设计模式,那我们首先封装build模式的shapeBuilder,这样做虽然代码比起直接使用apply{}要多,但是可以让纯java项目用起来很舒服,其他实现请查看源码:

  1. class ShapeBuilder : DrawableBuilder { 
  2.     private var mRadius = 0f 
  3.     private var mWidth = 0f 
  4.     private var mHeight = 0f 
  5.     ... 
  6.     private var mShape = GradientDrawable.RECTANGLE 
  7.     private var mSolidColor = 0 
  8.  
  9.     /**分别设置四个角的圆角*/ 
  10.     fun corner(leftTop: Float,rightTop: Float,leftBottom: Float,rightBottom: Float): ShapeBuilder { 
  11.         ....if(dp)dp2px(leftTop) else leftTop 
  12.         return this 
  13.     } 
  14.  
  15.     fun solid(@ColorRes colorId: Int): ShapeBuilder { 
  16.         mSolidColor = ContextCompat.getColor(context, colorId) 
  17.         return this 
  18.     } 
  19.     // 省略其他参数设置方法 详细代码查看源码 
  20.     override fun build(): Drawable { 
  21.         val gradientDrawable = GradientDrawable() 
  22.         gradientDrawable = GradientDrawable() 
  23.         gradientDrawable.setColor(mSolidColor) 
  24.         gradientDrawable.shape = mShape 
  25.         ....其他参数设置 
  26.         return gradientDrawable 
  27.     }     

把build模式转换为dsl

理论上所有的build模式都可以轻松转换为dsl写法:

  1. inline fun shapeDrawable(builder: ShapeBuilder.() -> Unit): Drawable { 
  2.     return ShapeBuilder().also(builder).build() 
  3. //使用方法  
  4. val drawable = shapeDrawable{ 
  5.     ... 

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

函数去括号

通过上面封装已经实现了dsl的写法,通常setBackground可以通过setter简化,但是我发现由于有些api设计还需要加括号,这样不太kotlin:

  1. //容易阅读 
  2. iv1.background = shapeDrawable { 
  3.     shape(ShapeBuilder.Shape.RECTANGLE) 
  4.     solid("#ABE2E3"
  5. //多了括号看起来不舒服 
  6. iv2.setImageDrawable(shapeDrawable { 
  7.     solid("#84232323"
  8. }) 

怎么去掉括号呢?有2种方式infix函数(中缀表达)和property setter

infix函数特点和规范:

  • Kotlin允许在不使用括号和点号的情况下调用函数
  • 必须只有一个参数
  • 必须是成员函数或扩展函数
  • 不支持可变参数和带默认值参数
  1. /**为所有ImageView添加扩展infix函数 来去掉括号*/ 
  2. infix fun ImageView.src(drawable: Drawable?) { 
  3.     this.setImageDrawable(drawable) 
  4. //使用如下 
  5. iv2 src shapeDrawable { 
  6.     shape(ShapeBuilder.Shape.OVAL) 
  7.     solid("#E3ABC2"

当然了代码是用来阅读的。个人认为如果我们大量使用infix函数,阅读困难会大大增加,所以建议函数命名必须可以直击函数功能,而且函数功能简单且单一。

property setter方式,主要使用kotlin可以简化setter为 变量 =来去括号:

  1. /**扩展变量*/ 
  2. var ImageView.src: Drawable 
  3.     get() = drawable 
  4.     set(value) { 
  5.         this.setImageDrawable(value) 
  6.     } 
  7. //使用如下    
  8. iv2.src = shapeDrawable { 
  9.     shape(ShapeBuilder.Shape.OVAL) 
  10.     solid("#E3ABC2"
  11. }  

感谢@叮凛凛 指点,欢迎大家讨论一起学习,共同进步。

优缺点

优点:

  • 代码直接创建比起xml方式可以提升性能
  • dsl方式比起build模式和调用方法设置更加简洁符合kotlin风格
  • 通过合适的代码管理可以复用这些代码,比xml管理方便

缺点:

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

后语

上面把的DrawableDsl基础用法介绍完了,欢迎大家使用,欢迎提Issues,记得给个star哦。Github链接:https://github.com/forJrking/DrawableDsl

原文地址:https://juejin.cn/post/6953472037012635655

延伸 · 阅读

精彩推荐
  • 编程技术简单、好懂的Svelte实现原理

    简单、好懂的Svelte实现原理

    本文会围绕一张流程图和两个Demo讲解,正确的食用方式是用电脑打开本文,跟着流程图、Demo一边看、一边敲、一边学...

    魔术师卡颂4822021-11-10
  • 编程技术真正聪明的程序员,总有办法不加班

    真正聪明的程序员,总有办法不加班

    工作效率提升了,就可以少加班了,聪明的程序员,总会有一堆可以提升编码效率的工具?当一种工具满足不了工作需求,就去探索新的,今天纬小创就给...

    今日头条12482021-03-04
  • 编程技术Delphi - Indy idMessage和idSMTP实现邮件的发送

    Delphi - Indy idMessage和idSMTP实现邮件的发送

    这篇文章主要介绍了Delphi - Indy idMessage和idSMTP实现邮件的发送,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...

    JJ_JeremyWu6592020-09-22
  • 编程技术从Context源码实现谈React性能优化

    从Context源码实现谈React性能优化

    这篇文章主要介绍Context的实现原理,源码层面掌握React组件的render时机,从而写出高性能的React组件,源码层面了解shouldComponentUpdate、React.memo、PureComponen...

    魔术师卡颂5312020-12-20
  • 编程技术AIOps,SRE工程师手中的利器

    AIOps,SRE工程师手中的利器

    AIOps开始成为一种极为重要的站点可靠性工程工具。它能够高效吸纳观察数据、参与数据以及来自第三方工具的数据,判断系统运行状态并保证其处于最佳...

    至顶网5972021-03-08
  • 编程技术用户态 Tcpdump 如何实现抓到内核网络包的?

    用户态 Tcpdump 如何实现抓到内核网络包的?

    在网络包的发送和接收过程中,绝大部分的工作都是在内核态完成的。那么问题来了,我们常用的运行在用户态的程序 tcpdump 是那如何实现抓到内核态的包...

    开发内功修炼11612021-09-08
  • 编程技术2021年值得关注的React PDF 库

    2021年值得关注的React PDF 库

    今天,许多网络应用程序为其用户提供内置的PDF浏览选项。然而,选择一个并不容易,因为它们的功能远远超过显示PDF。在这篇文章中,我将评估5个React的...

    TianTianUp5232021-06-21
  • 编程技术让开发效率倍增的 VS Code 插件

    让开发效率倍增的 VS Code 插件

    今天来分享一些提升开发效率的实用 VS Code 插件!Better Comments 扩展可以帮助我们在代码中创建更人性化的注释,有不同形式和颜色的注释供我们选择。 ...

    前端充电宝7132022-04-21