厲害了!Android drawable竟然還能這么寫?
微信改了推動機(jī)制,真愛請星標(biāo)本公號 公眾號回復(fù)加入BATcoder技術(shù)群 BAT
作者:forJrking
https://juejin.cn/user/2612095355987191
前言
通常我們在res/drawable下面自定義shape和selector來滿足一些UI的設(shè)計,但是由于xml最終轉(zhuǎn)換為drawable需要經(jīng)過IO或反射創(chuàng)建,會有一些性能損耗,另外隨著項目的增大和模塊化等,很多通用的樣式并不能快速復(fù)用,需要合理的項目資源管理規(guī)范才能實施。那么通過代碼直接創(chuàng)建這些drawable,可以在一定程度上降低這些副作用。本篇介紹用kotlin DSL簡潔的語法特性來實現(xiàn)常見的drawable。
代碼對應(yīng)效果預(yù)覽





集成和使用
在項目級的build.gradle文件種添加倉庫Jitpack
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
添加依賴
dependencies {
implementation 'com.github.forJrking:DrawableDsl:0.0.3’
}
拋棄xml創(chuàng)建方式示例(其他參見demo)
// infix用法用于去掉括號更加簡潔,詳細(xì)后面說明
image src shapeDrawable {
//指定shape樣式
shape(ShapeBuilder.Shape.RECTANGLE)
//圓角,支持4個角單獨設(shè)置
corner(20f)
//solid 顏色
solid("#ABE2E3")
//stroke 顏色,邊框dp,虛線設(shè)置
stroke(R.color.white, 2f, 5f, 8f)
}
//按鈕點擊樣式
btn.background = selectorDrawable {
//默認(rèn)樣式
normal = shapeDrawable {
corner(20f)
gradient(90, R.color.F97794, R.color.C623AA2)
}
//點擊效果
pressed = shapeDrawable {
corner(20f)
solid("#84232323")
}
}
實現(xiàn)思路
xml如何轉(zhuǎn)換成drawable
xml變成drawable,通過android.graphics.drawable.DrawableInflater這個類來IO解析標(biāo)簽創(chuàng)建,然后通過解析標(biāo)簽再設(shè)置屬性:
//標(biāo)簽創(chuàng)建
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();
...
}
}
//反射創(chuàng)建
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) {
...
}
代碼實現(xiàn)
由于創(chuàng)建shape等需要設(shè)置各種屬性來構(gòu)建,比較符合build設(shè)計模式,那我們首先封裝build模式的shapeBuilder,這樣做雖然代碼比起直接使用apply{}要多,但是可以讓純java項目用起來很舒服,其他實現(xiàn)請查看源碼:
class ShapeBuilder : DrawableBuilder {
private var mRadius = 0f
private var mWidth = 0f
private var mHeight = 0f
...
private var mShape = GradientDrawable.RECTANGLE
private var mSolidColor = 0
/**分別設(shè)置四個角的圓角*/
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
}
// 省略其他參數(shù)設(shè)置方法 詳細(xì)代碼查看源碼
override fun build(): Drawable {
val gradientDrawable = GradientDrawable()
gradientDrawable = GradientDrawable()
gradientDrawable.setColor(mSolidColor)
gradientDrawable.shape = mShape
....其他參數(shù)設(shè)置
return gradientDrawable
}
}
把build模式轉(zhuǎn)換為dsl
理論上所有的build模式都可以輕松轉(zhuǎn)換為dsl寫法:
inline fun shapeDrawable(builder: ShapeBuilder.() -> Unit): Drawable {
return ShapeBuilder().also(builder).build()
}
//使用方法
val drawable = shapeDrawable{
...
}
函數(shù)去括號
通過上面封裝已經(jīng)實現(xiàn)了dsl的寫法,通常setBackground可以通過setter簡化,但是我發(fā)現(xiàn)由于有些api設(shè)計還需要加括號,這樣不太kotlin:
//容易閱讀
iv1.background = shapeDrawable {
shape(ShapeBuilder.Shape.RECTANGLE)
solid("#ABE2E3")
}
//多了括號看起來不舒服
iv2.setImageDrawable(shapeDrawable {
solid("#84232323")
})
怎么去掉括號呢?有2種方式infix函數(shù)(中綴表達(dá))和property setter
infix函數(shù)特點和規(guī)范
-
Kotlin允許在不使用括號和點號的情況下調(diào)用函數(shù) -
必須只有一個參數(shù) -
必須是成員函數(shù)或擴(kuò)展函數(shù) -
不支持可變參數(shù)和帶默認(rèn)值參數(shù)
/**為所有ImageView添加擴(kuò)展infix函數(shù) 來去掉括號*/
infix fun ImageView.src(drawable: Drawable?) {
this.setImageDrawable(drawable)
}
//使用如下
iv2 src shapeDrawable {
shape(ShapeBuilder.Shape.OVAL)
solid("#E3ABC2")
}
/**擴(kuò)展變量*/
var ImageView.src: Drawable
get() = drawable
set(value) {
this.setImageDrawable(value)
}
//使用如下
iv2.src = shapeDrawable {
shape(ShapeBuilder.Shape.OVAL)
solid("#E3ABC2")
}
優(yōu)缺點
-
代碼直接創(chuàng)建比起xml方式可以提升性能 -
dsl方式比起build模式和調(diào)用方法設(shè)置更加簡潔符合kotlin風(fēng)格 -
通過合適的代碼管理可以復(fù)用這些代碼,比xml管理方便
-
沒有as的預(yù)覽功能,只有通過上機(jī)觀測 -
api還沒有覆蓋所有drawable屬性(例如shape = ring等)
結(jié)語
https://github.com/forJrking/DrawableDsl
推薦閱讀
? 耗時2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!
? 『BATcoder』做了多年安卓還沒編譯過源碼?一個視頻帶你玩轉(zhuǎn)!
BATcoder技術(shù)群,讓一部分人先進(jìn)大廠
大家好,我是劉望舒,騰訊TVP,著有三本技術(shù)暢銷書,連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,谷歌開發(fā)者社區(qū)特邀講師。
前華為技術(shù)專家,現(xiàn)大廠技術(shù)負(fù)責(zé)人。
想要加入 BATcoder技術(shù)群,公號回復(fù)BAT 即可。
為了防止失聯(lián),歡迎關(guān)注我的小號
微信改了推送機(jī)制,真愛請星標(biāo)本公號??
