前言
通常我们在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用法用于去掉括号更加简洁,详细后面说明
- imagesrcshapeDrawable{
- //指定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解析标签创建,然后通过解析标签再设置属性:
- //标签创建
- privateDrawableinflateFromTag(@NonNullStringname){
- switch(name){
- case"selector":
- returnnewStateListDrawable();
- case"level-list":
- returnnewLevelListDrawable();
- case"layer-list":
- returnnewLayerDrawable();
- ....
- case"color":
- returnnewColorDrawable();
- case"shape":
- returnnewGradientDrawable();
- case"vector":
- returnnewVectorDrawable();
- ...
- }
- }
- //反射创建
- privateDrawableinflateFromClass(@NonNullStringclassName){
- try{
- Constructor<?extendsDrawable>constructor;
- synchronized(CONSTRUCTOR_MAP){
- constructor=CONSTRUCTOR_MAP.get(className);
- if(constructor==null){
- finalClass<?extendsDrawable>clazz=mClassLoader.loadClass(className).asSubclass(Drawable.class);
- constructor=clazz.getConstructor();
- CONSTRUCTOR_MAP.put(className,constructor);
- }
- }
- returnconstructor.newInstance();
- }catch(NoSuchMethodExceptione){
- ...
- }
代码实现
由于创建shape等需要设置各种属性来构建,比较符合build设计模式,那我们首先封装build模式的shapeBuilder,这样做虽然代码比起直接使用apply{}要多,但是可以让纯java项目用起来很舒服,其他实现请查看源码:
- classShapeBuilder:DrawableBuilder{
- privatevarmRadius=0f
- privatevarmWidth=0f
- privatevarmHeight=0f
- ...
- privatevarmShape=GradientDrawable.RECTANGLE
- privatevarmSolidColor=0
- /**分别设置四个角的圆角*/
- funcorner(leftTop:Float,rightTop:Float,leftBottom:Float,rightBottom:Float):ShapeBuilder{
- ....if(dp)dp2px(leftTop)elseleftTop
- returnthis
- }
- funsolid(@ColorRescolorId:Int):ShapeBuilder{
- mSolidColor=ContextCompat.getColor(context,colorId)
- returnthis
- }
- //省略其他参数设置方法详细代码查看源码
- overridefunbuild():Drawable{
- valgradientDrawable=GradientDrawable()
- gradientDrawable=GradientDrawable()
- gradientDrawable.setColor(mSolidColor)
- gradientDrawable.shape=mShape
- ....其他参数设置
- returngradientDrawable
- }
- }
把build模式转换为dsl
理论上所有的build模式都可以轻松转换为dsl写法:
- inlinefunshapeDrawable(builder:ShapeBuilder.()->Unit):Drawable{
- returnShapeBuilder().also(builder).build()
- }
- //使用方法
- valdrawable=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函数来去掉括号*/
- infixfunImageView.src(drawable:Drawable?){
- this.setImageDrawable(drawable)
- }
- //使用如下
- iv2srcshapeDrawable{
- shape(ShapeBuilder.Shape.OVAL)
- solid("#E3ABC2")
- }
当然了代码是用来阅读的。个人认为如果我们大量使用infix函数,阅读困难会大大增加,所以建议函数命名必须可以直击函数功能,而且函数功能简单且单一。
property setter方式,主要使用kotlin可以简化setter为 变量 =来去括号:
- /**扩展变量*/
- varImageView.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
原文地址:https://juejin.cn/post/6953472037012635655








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