当前位置:首页 > 通信资讯 > 正文

kotlin设计模式(kotlin 范型)

前言

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

kotlin设计模式(kotlin 范型)

代码对应效果预览

kotlin设计模式(kotlin 范型)

kotlin设计模式(kotlin 范型)

kotlin设计模式(kotlin 范型)

kotlin设计模式(kotlin 范型)

集成和使用

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

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

添加依赖

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

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

  1. //infix用法用于去掉括号更加简洁,详细后面说明
  2. imagesrcshapeDrawable{
  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. //按钮点击样式
  13. btn.background=selectorDrawable{
  14. //默认样式
  15. normal=shapeDrawable{
  16. corner(20f)
  17. gradient(90,R.color.F97794,R.color.C623AA2)
  18. }
  19. //点击效果
  20. pressed=shapeDrawable{
  21. corner(20f)
  22. solid("#84232323")
  23. }
  24. }

实现思路

xml如何转换成drawable

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

  1. //标签创建
  2. privateDrawableinflateFromTag(@NonNullStringname){
  3. switch(name){
  4. case"selector":
  5. returnnewStateListDrawable();
  6. case"level-list":
  7. returnnewLevelListDrawable();
  8. case"layer-list":
  9. returnnewLayerDrawable();
  10. ....
  11. case"color":
  12. returnnewColorDrawable();
  13. case"shape":
  14. returnnewGradientDrawable();
  15. case"vector":
  16. returnnewVectorDrawable();
  17. ...
  18. }
  19. }
  20. //反射创建
  21. privateDrawableinflateFromClass(@NonNullStringclassName){
  22. try{
  23. Constructor<?extendsDrawable>constructor;
  24. synchronized(CONSTRUCTOR_MAP){
  25. constructor=CONSTRUCTOR_MAP.get(className);
  26. if(constructor==null){
  27. finalClass<?extendsDrawable>clazz=mClassLoader.loadClass(className).asSubclass(Drawable.class);
  28. constructor=clazz.getConstructor();
  29. CONSTRUCTOR_MAP.put(className,constructor);
  30. }
  31. }
  32. returnconstructor.newInstance();
  33. }catch(NoSuchMethodExceptione){
  34. ...
  35. }

代码实现

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

  1. classShapeBuilder:DrawableBuilder{
  2. privatevarmRadius=0f
  3. privatevarmWidth=0f
  4. privatevarmHeight=0f
  5. ...
  6. privatevarmShape=GradientDrawable.RECTANGLE
  7. privatevarmSolidColor=0
  8. /**分别设置四个角的圆角*/
  9. funcorner(leftTop:Float,rightTop:Float,leftBottom:Float,rightBottom:Float):ShapeBuilder{
  10. ....if(dp)dp2px(leftTop)elseleftTop
  11. returnthis
  12. }
  13. funsolid(@ColorRescolorId:Int):ShapeBuilder{
  14. mSolidColor=ContextCompat.getColor(context,colorId)
  15. returnthis
  16. }
  17. //省略其他参数设置方法详细代码查看源码
  18. overridefunbuild():Drawable{
  19. valgradientDrawable=GradientDrawable()
  20. gradientDrawable=GradientDrawable()
  21. gradientDrawable.setColor(mSolidColor)
  22. gradientDrawable.shape=mShape
  23. ....其他参数设置
  24. returngradientDrawable
  25. }
  26. }

把build模式转换为dsl

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

  1. inlinefunshapeDrawable(builder:ShapeBuilder.()->Unit):Drawable{
  2. returnShapeBuilder().also(builder).build()
  3. }
  4. //使用方法
  5. valdrawable=shapeDrawable{
  6. ...
  7. }

备注: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. //多了括号看起来不舒服
  7. iv2.setImageDrawable(shapeDrawable{
  8. solid("#84232323")
  9. })

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

infix函数特点和规范:

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

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

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

  1. /**扩展变量*/
  2. varImageView.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

如果您对该产品感兴趣,请填写办理(客服微信:xiaoxiongyidong)

为您推荐:

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。