Kotlin DSL
Kotlin DSL
Kotlin DSL
如何实现 DSL
高阶函数实现大括号调用
常见的 DSL 都会用大括号来表现层级。Kotlin 的高阶函数允许指定一个 lambda 类型的参数,且当 lambda 位于参数列表的最后位置时可以省略圆括号,满足 DSL 中的大括号语法要求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 原有代码:
LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
addView(ImageView(context))
}
// 改造后
HorizontalLayout(context) {
...
it.addView(ImageView(context) {
...
})
}
// 改造
fun HorizontalLayout(context: Context, init: (LinearLayout) -> Unit) : LinearLayout {
return LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
init(this)
}
}
通过 Receiver 传递上下文
将 lambda 的参数改为 Receiver,大括号中对 it 的引用可以变为 this 并直接省略
1
2
3
4
5
6
7
8
9
fun HorizontalLayout(context: Context, init: LinearLayout.() -> Unit) : LinearLayout {
return LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
init()
}
}
fun ViewGroup.ImageView(init: ImageView.() -> Unit) {
addView(ImageView(context).apply(init))
}
使用:
1
2
3
4
5
6
HorizontalLayout {
...
ImageView {
...
}
}
infix 增强可读性
Kotlin 的中缀函数可以让函数省略圆点以及圆括号等程序符号,让语句更自然,进一步提升可读性。
现有代码:
1
2
3
4
HorizontalLayout {
setTag(1,"a")
setTag(2,"b")
}
infix 改造:
1
2
3
4
5
6
7
class _Tag(val view: View) {
infix fun <B> Int.to(that: B) = view.setTag(this, that)
}
fun View.tag(block: _Tag.() -> Unit) {
_Tag(this).apply(block)
}
改造后:
1
2
3
4
5
6
HorizontalLayout {
tag {
1 to "a"
2 to "b"
}
}
@DslMarker 限制作用域
Kotlin 为 DSL 的使用场景提供了 @DslMarker
注解,可以对方法的作用域进行限制。添加注解的 lambda 中在省略 this 的隐式调用时只能访问到最近的 Receiver 类型,当调用更外层的 Receiver 的方法会报错。
@DslMarker
是一个元注解。
1
2
3
4
5
6
@DslMarker
@Target(AnnotationTarget.TYPE)
annotation class ViewDslMarker
fun ViewGroup.TextView(init: (@ViewDslMarker TextView).() -> Unit) {
addView(TextView(context).apply(init))
}
Context Receivers 传递多个上下文 (Kotlin 1.6.20-M1 及以上)
Context Receivers 是刚刚在 Kotlin 1.6.20-M1 中发布的新语法,它使函数定义时拥有多个 Receiver 成为可能。
1
2
3
4
5
6
7
context(View)
val Float.dp
get() = this * this@View.resources.displayMetrics.density
class SomeView : View {
val someDimension = 4f.dp
}
使用 inline 和 @PublishedApi 提高性能
- DSL 的实现使用了大量高阶函数,过多的 lambda 会产生过的匿名类,同时也会增加运行时对象创建的开销,不少 DSL 选择使用 inline 操作符,减少匿名类的产生,提高运行时性能。
- inline 函数内部调用的函数必须是 public 的,这会造成一些不必要的代码暴露,此时可以借助
@PublishedApi
化解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//resInt 指定图片
inline fun ViewGroup.ImageView(resId: Int, init: ImageView.() -> Unit) {
_ImageView(init).apply { setImageResource(resId) }
}
//drawable 指定图片
inline fun ViewGroup.ImageView(drawable: Drawable, init: ImageView.() -> Unit) {
_ImageView(init).apply { setImageDrawable(drawable) }
}
@PublishedApi
internal inline fun ViewGroup._ImageView(init: ImageView.() -> Unit) =
ImageView(context).apply {
this@_ImageView.addView(this)
init()
}
为了方便 DSL 中使用,我们定义了两个 ImageView 方法,分别用于 resId 和 drawable 的图片设置。由于大部分代码可以复用,我们抽出了一个 ImageView 方法。但是由于要在 inline 方法中使用,所以编译器要求 ImageView 必须是 public 类型。可能的值为 ImageView 只需在库的内部服务,所以可以添加为 internal 的同时加 @PublishdApi 注解,它允许一个模块内部方法在 inline 中使用,且编译器不会报错。
DSL 总结
- 使用带尾 lambda 的高阶函数实现大括号的层级调用
- 为 lambda 添加 Receiver,通过 this 传递上下文,避免使用 it
- 通过扩展函数优化代码风格,DSL 中避免出现命令式的语义
- 使用 infix 减少点
.
和圆括号()
等符号的出现,提高可读性 - 使用
@DslMarker
限制 DSL 作用域,只在最近的 Receiver,避免出错 - 使用 Context Receivers 传递多个上下文,DSL 更聪明(非正式语法,未来有变动的可能)
- 使用 inline 提升性能,同时使用 @PublishedApi 避免不必要的代码暴露
kotlin 优雅的封装匿名内部类(DSL、高阶函数)
默认方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private var etString: EditText? = null
private fun oldTextWatcher() {
etString?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
}
})
}
fun interface?
DSL 方式
- 创建接口实现类:XxxxInterfaceDslImpl 实现 TextWatcher
- 原接口方法对应的 Kotlin 函数对象 (即高阶函数),函数对象的签名与对应的方法签名保持一致。
- DSL 函数,函数名称、签名都与原接口的方法一一对应,用于接收 lambda 赋值给 Kotlin 函数对象。
- 原接口方法的实现,每个接口方法的实现,都是对实现类中 Kotlin 函数对象的调用。
- 创建与原函数同名的扩展函数,函数参数为实现类扩展函数
- 使用
- 创建接口实现类:XxxxInterfaceDslImpl 实现 TextWatcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class TextWatcherDslImpl : TextWatcher {
// 原接口对应的kotlin函数对象
private var afterTextChanged: ((Editable?) -> Unit)? = null
private var beforeTextChanged: ((CharSequence?, Int, Int, Int) -> Unit)? = null
private var onTextChanged: ((CharSequence?, Int, Int, Int) -> Unit)? = null
/**
* DSL中使用的函数,一般保持同名即可
*/
fun afterTextChanged(method: (Editable?) -> Unit) {
afterTextChanged = method
}
fun beforeTextChanged(method: (CharSequence?, Int, Int, Int) -> Unit) {
beforeTextChanged = method
}
fun onTextChanged(method: (CharSequence?, Int, Int, Int) -> Unit) {
onTextChanged = method
}
/**
* 实现原接口的函数
*/
override fun afterTextChanged(s: Editable?) {
afterTextChanged?.invoke(s)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
beforeTextChanged?.invoke(s, start, count, after)
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
onTextChanged?.invoke(s, start, before, count)
}
}
- 创建与原函数同名的扩展函数,函数参数为实现类扩展函数
1
2
3
4
5
fun TextView.addTextChangedListenerDsl(init: TextWatcherDslImpl.() -> Unit) {
val listener = TextWatcherDslImpl()
init(listener)
this.addTextChangedListener(listener)
}
- 使用:
1
2
3
4
5
6
7
8
9
10
11
private fun dslTextWatcher1() {
etString?.addTextChangedListenerDsl {
afterTextChanged {
if (it.toString().length >= 4) {
KeyboardUtils.toggleSoftInput()
}
}
onTextChanged { charSequence, i, i2, i3 ->
}
}
}
高阶函数方式
- 高阶函数封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
inline fun TextView.addTextChangedListenerClosure(
crossinline afterTextChanged: (Editable?) -> Unit = {},
crossinline beforeTextChanged: (CharSequence?, Int, Int, Int) -> Unit = { _, _, _, _ -> },
crossinline onTextChanged: (CharSequence?, Int, Int, Int) -> Unit = { _, _, _, _ -> }
) {
val listener = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
afterTextChanged.invoke(s)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
beforeTextChanged.invoke(s, start, count, after)
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
onTextChanged.invoke(s, start, before, count)
}
}
this.addTextChangedListener(listener)
}
- 使用:
1
2
3
4
5
6
7
8
9
10
11
private fun dslTextWatcher2() {
etString?.addTextChangedListenerClosure(
afterTextChanged = {
if (it.toString().length >= 4) {
KeyboardUtils.toggleSoftInput()
}
},
onTextChanged = { charSequence, i, i2, i3 ->
}
)
}
动态代理 + 类委托方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
inline fun <reified T : Any> noOpDelegate(): T {
val javaClass = T::class.java
return Proxy.newProxyInstance(
javaClass.classLoader,
arrayOf(javaClass),
NO_OP_HANDLER
) as T
}
val NO_OP_HANDLER = InvocationHandler { proxy, method, args ->
// no op
}
// 复写的方法会回调,未复写的默认处理(不回调)
registerActivityLifecycleCallbacks(object :
Application.ActivityLifecycleCallbacks by noOpDelegate<ActivityLifecycleCallbacks>() {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
Log.i("hacket.activity", "onActivityCreated:" + activity.localClassName)
}
override fun onActivityDestroyed(activity: Activity) {
Log.w("hacket.activity", "onActivityDestroyed:" + activity.localClassName)
}
})
DSL 应用
对象初始化赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
data class User(var name: String = "", var age: Int = 0) {
override fun toString(): String {
return "My name is $name ,i am $age years old"
}
}
private fun testUser() {
val user = User("hacket1", 30)
println(user)
val user2 = User().apply {
name = "hacket2"
age = 30
if (age < 30) {
return
}
}
println(user2)
val user3 = create {
name = "hacket3"
age = 30
}
println(user3)
}
inline fun create(crossinline initBlock: User.() -> Unit): User {
return User().also { // 或者用apply
initBlock.invoke(it)
}
}
封装匿名内部类有多个实现方法的情况
- TextView 的 TextWatcher
- TabLayout Listener
TextView 的 Span 封装
思路:
- 它应该是 TextView 的一个扩展函数
- 它的内部是 DSL 风格的代码
- 它的每段文字都有设置颜色 & 点击事件的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
interface DslSpannableStringBuilder {
//增加一段文字
fun addText(text: String, method: (DslSpanBuilder.() -> Unit)? = null)
}
interface DslSpanBuilder {
//设置文字颜色
fun setColor(color: String)
//设置点击事件
fun onClick(useUnderLine: Boolean = true, onClick: (View) -> Unit)
}
//为 TextView 创建扩展函数,其参数为接口的扩展函数
fun TextView.buildSpannableString(init: DslSpannableStringBuilder.() -> Unit) {
//具体实现类
val spanStringBuilderImpl = DslSpannableStringBuilderImpl()
spanStringBuilderImpl.init()
movementMethod = LinkMovementMethod.getInstance()
//通过实现类返回SpannableStringBuilder
text = spanStringBuilderImpl.build()
}
class DslSpannableStringBuilderImpl : DslSpannableStringBuilder {
private val builder = SpannableStringBuilder()
//记录上次添加文字后最后的索引值
var lastIndex: Int = 0
var isClickable = false
override fun addText(text: String, method: (DslSpanBuilder.() -> Unit)?) {
val start = lastIndex
builder.append(text)
lastIndex += text.length
val spanBuilder = DslSpanBuilderImpl()
method?.let { spanBuilder.it() }
spanBuilder.apply {
onClickSpan?.let {
builder.setSpan(it, start, lastIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
isClickable = true
}
if (!useUnderLine) {
val noUnderlineSpan = NoUnderlineSpan()
builder.setSpan(noUnderlineSpan, start, lastIndex, Spanned.SPAN_MARK_MARK)
}
foregroundColorSpan?.let {
builder.setSpan(it, start, lastIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
fun build(): SpannableStringBuilder {
return builder
}
}
class DslSpanBuilderImpl : DslSpanBuilder {
var foregroundColorSpan: ForegroundColorSpan? = null
var onClickSpan: ClickableSpan? = null
var useUnderLine = true
override fun setColor(color: String) {
foregroundColorSpan = ForegroundColorSpan(Color.parseColor(color))
}
override fun onClick(useUnderLine: Boolean, onClick: (View) -> Unit) {
onClickSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
onClick(widget)
}
}
this.useUnderLine = useUnderLine
}
}
class NoUnderlineSpan : UnderlineSpan() {
override fun updateDrawState(ds: TextPaint) {
ds.color = ds.linkColor
ds.isUnderlineText = false
}
}
使用:
1
2
3
4
5
6
7
8
9
tvTestDsl.buildSpannableString {
addText("我已详细阅读并同意")
addText("《隐私政策》"){
setColor("#0099FF")
onClick(false) {
//do some thing
}
}
}
封装 ConfirmDialog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package club.jinmei.mgvoice.core.widget
import android.content.DialogInterface
import android.os.Bundle
import android.view.Gravity
import android.widget.CompoundButton
import androidx.annotation.LayoutRes
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import club.jinmei.mgvoice.core.R
inline fun showConfirmDialogDSL(
act: FragmentActivity?,
crossinline initBlock: ConfirmDialogInit.() -> Unit
) {
val confirmDialogInitImpl = ConfirmDialogInitImpl()
confirmDialogInitImpl.initBlock()
val dialog = confirmDialogInitImpl.build()
dialog.show(act)
}
interface ConfirmDialogInit {
/**
* 提供了LifecycleOwner。在onDestroy dismiss
*/
var lifecycleOwner: LifecycleOwner?
// region 基础属性 setArguments
var title: String?
var subTitle: String?
var message: String
var messageHasMoreLines: Boolean
var multiTextView: Boolean
var textMessageGravity: Int
// endregion
var layoutId: Int
var canOutCancelable: Boolean
var okText: String?
var cancelText: String?
var isCancelAvailable: Boolean
var checkText: CharSequence?
var textGravity: Int
var messageGravity: Int?
var tipText: CharSequence?
/**
* OK按钮listener 返回false dismiss
*/
var onOkListener: (ConfirmDialog.() -> Boolean)
/**
* Cancel按钮listener 返回false dismiss
*/
var onCancelListener: (ConfirmDialog.() -> Boolean)
var onCheckChangeListener: ((CompoundButton?, Boolean) -> Unit)
var onDismissListener: (DialogInterface?.() -> Unit)?
}
class ConfirmDialogInitImpl(
@LayoutRes
override var title: String? = "",
override var subTitle: String? = "",
override var message: String = "",
override var messageHasMoreLines: Boolean = false,
override var multiTextView: Boolean = false,
override var textMessageGravity: Int = 0,
override var layoutId: Int = R.layout.common_confirm_layout,
override var canOutCancelable: Boolean = true,
override var okText: String? = null,
override var cancelText: String? = null,
override var isCancelAvailable: Boolean = true,
override var checkText: CharSequence? = null,
override var textGravity: Int = Gravity.CENTER,
override var messageGravity: Int? = null,
override var tipText: CharSequence? = null,
override var onOkListener: (ConfirmDialog.() -> Boolean) = { false },
override var onCancelListener: (ConfirmDialog.() -> Boolean) = { false },
override var onCheckChangeListener: (CompoundButton?, Boolean) -> Unit = { _, _ -> },
override var onDismissListener: (DialogInterface?.() -> Unit)? = null,
override var lifecycleOwner: LifecycleOwner? = null
) : ConfirmDialogInit {
fun build(): ConfirmDialog {
val dialog = ConfirmDialog()
val args = Bundle()
args.putString("title", title)
args.putString("sub_title", subTitle)
args.putString("message", message)
args.putBoolean("more_lines", messageHasMoreLines)
args.putInt("text_message_gravity", textMessageGravity)
args.putBoolean("multi_text", multiTextView)
dialog.arguments = args
with(dialog) {
setLayoutId(layoutId)
setCanOutCancalable(canOutCancelable)
setOkText(okText)
setCancelText(cancelText)
setCancelAvailable(isCancelAvailable)
setGravity(textGravity)
setTips(tipText)
messageGravity?.let {
setMessageGravity(it)
}
setOnConfirmClickListener(object : ConfirmDialog.OnConfirmClickListener() {
override fun onOk(confirmDialog: ConfirmDialog): Boolean {
return onOkListener.invoke(confirmDialog)
}
override fun onCancel(confirmDialog: ConfirmDialog): Boolean {
return onCancelListener.invoke(confirmDialog)
}
})
setCheckable(
checkText
) { buttonView, isChecked -> onCheckChangeListener.invoke(buttonView, isChecked) }
onDismissListener?.let {
setOnDismissListener { dialog -> it.invoke(dialog) }
}
}
lifecycleOwner?.let {
it.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
if (dialog.isVisible) {
dialog.dismiss()
}
}
})
}
return dialog
}
}
// fun test(activity: FragmentActivity) {
// showConfirmDialogDSL(activity) {
// message = "确定取消吗?"
// onOkListener = {
// Toast.makeText(activity, "点击了确定", Toast.LENGTH_SHORT).show()
// false
// }
// }
//
// showConfirmDialogDSL(activity) {
// title = "title"
// message = "message"
// subTitle = "subTitle"
// message = "message"
// onOkListener = {
// false
// }
// onCancelListener = {
// true
// }
// onCheckChangeListener = { _, _ -> }
// onDismissListener = {
// // dismiss
// }
// }
// }
未用 DSL 之前:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var confirmDialog: ConfirmDialog = ConfirmDialog.newInstance(
resources.getString(R.string.title_for_black_person),
resources.getString(R.string.message_for_black_person), true, true
)
confirmDialog.setCancelText(getString(R.string.common_cancel))
confirmDialog.setOkText(getString(R.string.common_ok))
confirmDialog.setOnConfirmClickListener(object : ConfirmDialog.OnConfirmClickListener() {
override fun onOk(confirmDialog: ConfirmDialog): Boolean {
callback.invoke()
confirmDialog?.dismiss()
return true
}
})
confirmDialog.show(this)
使用 DSL:
1
2
3
4
5
6
7
8
9
10
11
12
13
showConfirmDialogDSL(this) {
lifecycleOwner = this@UserHomeActivity
title = resources.getString(R.string.title_for_black_person)
message = resources.getString(R.string.message_for_black_person)
messageHasMoreLines = true
multiTextView = true
cancelText = getString(R.string.common_cancel)
okText = getString(R.string.common_ok)
onOkListener = {
callback.invoke()
false
}
}
封装 ImageLoader 加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
object UserImageLoader {
const val TAG = "UserImageLoader"
inline fun loadDSL(
url: String? = null,
view: SimpleDraweeView? = null,
ratio: Float? = null,
fillType: ImageFillType? = null,
crossinline onInit: (UserOnImageLoadInit.() -> Unit) = { },
) {
val init = UserOnImageLoadInit()
onInit.invoke(init)
val urlInner = if (!url.isNullOrEmpty()) url else init.url
val viewInner = view ?: init.view
val ratioInner = ratio ?: init.ratio
if (urlInner.isNullOrEmpty() || viewInner !is SimpleDraweeView) {
return
}
val fillTypeInner = fillType ?: init.fillType
val loadConfig: SImageLoader.LoadConfig = if (fillTypeInner == ImageFillType.MASK) {
// 补图时,默认使用蒙层补图方式
SImageLoader.LoadConfigTemplate.MASK.getConfig()
.copy(
isNeedCut = true,
aspectRatio = ratioInner,
width = viewInner.width.default(),
onImageLoadListener = init,
)
} else {
// 不补图时的默认配置
SImageLoader.LoadConfigTemplate.DEFAULT.getConfig()
.copy(
isNeedCut = true,
aspectRatio = ratioInner,
width = viewInner.width.default(),
onImageLoadListener = init,
)
}
SImageLoader.load(
url = urlInner.default(),
view = viewInner,
loadConfig = loadConfig
)
}
class UserOnImageLoadInit : OnImageLoadListener {
var url: String? = ""
var view: SimpleDraweeView? = null
var ratio: Float? = null
var fillType: ImageFillType = ImageFillType.NONE
private var onImageLoadStartBlock: ((url: String) -> Unit)? = null
private var onImageLoadSuccessBlock: ((
url: String, width: Int, height: Int,
animatable: Animatable?
) -> Unit)? = null
private var onFailureBlock: ((url: String, throwable: Throwable) -> Unit)? = null
private var onImageDecodeSuccessBlock: ((url: String, bitmap: Bitmap) -> Unit)? = null
fun onImageLoadStart(block: ((url: String) -> Unit)? = null) {
this.onImageLoadStartBlock = block
}
fun onImageLoadSucces(
block: ((
url: String, width: Int, height: Int,
animatable: Animatable?
) -> Unit)? = null
) {
this.onImageLoadSuccessBlock = block
}
fun onFailure(block: ((url: String, throwable: Throwable) -> Unit)? = null) {
this.onFailureBlock = block
}
fun onImageDecodeSuccess(block: ((url: String, bitmap: Bitmap) -> Unit)? = null) {
this.onImageDecodeSuccessBlock = block
}
override fun onImageLoadStart(url: String) {
onImageLoadStartBlock?.invoke(url)
}
override fun onImageLoadSucces(
url: String,
width: Int,
height: Int,
animatable: Animatable?
) {
onImageLoadSuccessBlock?.invoke(url, width, height, animatable)
}
override fun onFailure(url: String, throwable: Throwable) {
onFailureBlock?.invoke(url, throwable)
}
override fun onImageDecodeSuccess(url: String, bitmap: Bitmap) {
onImageDecodeSuccessBlock?.invoke(url, bitmap)
}
}
}
使用:
1
2
3
4
5
6
7
8
9
10
UserImageLoader.loadDSL {
url = icon
view = getIv(mode)
onImageDecodeSuccess { url, bitmap ->
}
onFailure { url, throwable ->
}
}
Ref
- Kotlin DSL 实战:像 Compose 一样写代码 https://juejin.cn/post/7069568821568208927
本文由作者按照 CC BY 4.0 进行授权