厉害了!Android drawable竟然还能这么写?
微信改了推动机制,真爱请星标本公号 公众号回复加入BATcoder技术群 BAT
作者:forJrking
https://juejin.cn/user/2612095355987191
前言
通常我们在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的写法,通常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")
}
/**扩展变量*/
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等)
结语
https://github.com/forJrking/DrawableDsl
推荐阅读
• 耗时2年,Android进阶三部曲第三部《Android进阶指北》出版!
• 『BATcoder』做了多年安卓还没编译过源码?一个视频带你玩转!
BATcoder技术群,让一部分人先进大厂
大家好,我是刘望舒,腾讯TVP,著有三本技术畅销书,连续四年蝉联电子工业出版社年度优秀作者,谷歌开发者社区特邀讲师。
前华为技术专家,现大厂技术负责人。
想要加入 BATcoder技术群,公号回复BAT
即可。
为了防止失联,欢迎关注我的小号
微信改了推送机制,真爱请星标本公号👇