做网站需要哪些基本功能,江苏江都建设集团有限公司网站,手机网站竞价,郑州英文网站建设如何在 kotlin 优雅的封装匿名内部类#xff08;DSL、高阶函数#xff09;匿名内部类在 Java 中是经常用到的一个特性#xff0c;例如在 Android 开发中的各种 Listener#xff0c;使用时也很简单#xff0c;比如#xff1a;//lambda
button.setOnClickListener(v - …如何在 kotlin 优雅的封装匿名内部类DSL、高阶函数匿名内部类在 Java 中是经常用到的一个特性例如在 Android 开发中的各种 Listener使用时也很简单比如//lambda
button.setOnClickListener(v - {//do some thing
});
//匿名内部类
button.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {//do some thing}
});只有一个函数的接口在 Java 和 Kotlin 中都可以很方便的使用 lambda 表达式来缩略但是如果接口含有多个函数使用起来就比较”不优雅“了例如etString.addTextChangedListener(object :TextWatcher{override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {TODO(Not yet implemented)}override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {TODO(Not yet implemented)}override fun afterTextChanged(s: Editable?) {TODO(Not yet implemented)}
})使用起来与 Java 基本差不多通过 object 关键字实现了一个匿名内部类这种方法没什么大问题例如上面的例子中三个回调函数并非每次都要使用很多场景可能只会用到其中一个或者几个其余的都是空实现每次都写这样一个匿名内部类只不过是不优雅而已。在 Kotlin 中我们可以有两种方式实现比较优雅的使用匿名内部类1. DSL2. 高阶函数DSLDSL 方式实现封装可以分为以下几步1.创建接口实现类XxxxInterfaceDslImpl还有上面的 TextWatcher 作为例子class TextWatcherDslImpl : TextWatcher {//原接口对应的kotlin函数对象private var afterTextChanged: ((Editable?) - Unit)? nullprivate var beforeTextChanged: ((CharSequence?, Int, Int, Int) - Unit)? nullprivate 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. 原接口方法对应的 Kotlin 函数对象函数对象的签名与对应的方法签名保持一致。2. DSL 函数函数名称、签名都与原接口的方法一一对应用于接收 lambda 赋值给 Kotlin 函数对象。3. 原接口方法的实现每个接口方法的实现都是对实现类中 Kotlin 函数对象的调用。2.创建与原函数同名的扩展函数函数参数为实现类扩展函数fun TextView.addTextChangedListenerDsl(init: TextWatcherDslImpl.() - Unit) {val listener TextWatcherDslImpl()listener.init()this.addTextChangedListener(listener)
}扩展函数与原函数同名可以方便使用者调用无需记忆其他函数名如果担心混淆可以在函数名后加上 Dsl 用以区分。该函数的参数是我们第一步创建的实现类的扩展函数这是为了实现 DSL 语法。3.使用etString.addTextChangedListenerDsl {afterTextChanged {if (it.toString().length 4) {KeyboardUtils.toggleSoftInput()}}
}使用这种方式时可以说相当之优雅我们只需要调用我们需要实现的接口方法即可不需要使用的接口方法默认空实现。高阶函数高阶函数方式比 DSL 方式更简单一点inline fun TextView.addTextChangedListenerClosure(crossinline afterTextChanged: (Editable?) - Unit {},crossinline beforeTextChanged: (CharSequence?, Int, Int, Int) - Unit { charSequence, start, count, after - },crossinline onTextChanged: (CharSequence?, Int, Int, Int) - Unit { charSequence, start, after, count - }
) {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)
}我们创建一个同名扩展函数使用 Closure 尾缀作为区分该函数的参数为与接口方法一一对应的 Kotlin 函数对象并给其默认值赋值为 {} 即空实现在函数体里通过 object 关键字构建匿名内部类实现对象在其接口方法实现中调用与之一一对应的 Kotlin 函数对象。使用方式上与普通的 Kotlin 高阶函数使用方式相同etString.addTextChangedListenerClosure(afterTextChanged {if (it.toString().length 4) {KeyboardUtils.toggleSoftInput()}},
)tips上面示例的扩展函数中我们使用了 inline 与 crossinline 两个关键字这是 Koltin 特有的。inline 关键字通常用于修饰高阶函数用于提升性能。crossinline 声明的 lambda 不允许局部返回用于避免调用者错误的使用 return 导致函数中断。提供一个示例代码亲自尝试一下也许可以更好的理解Test
fun testInline() {testClosure {return}
}
private inline fun testClosure(test: (String) - String ) {println(step 1)println(test(step test))println(step 2)
}
在 Kotlin 中巧妙的使用 DSL 封装 SpannableStringBuilder源从何来在 Android 开发中 Spannable 实现富文本显示也算是一个比较常见的使用场景例如在登录页显示《隐私政策》、《服务协议》通常这是一个有自定义颜色与点击事件的 Span使用起来大致需要写如下代码private fun agreePrivate() {val tv findViewByIdTextView(R.id.tv_agree)val builder SpannableStringBuilder()val text 我已详细阅读并同意《隐私政策》builder.append(text)//设置span点击事件val clickableSpan object :ClickableSpan(){override fun onClick(widget: View) {//do some thing}}builder.setSpan(clickableSpan, 9, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)//设置span无下划线val noUnderlineSpan NoUnderlineSpan()builder.setSpan(noUnderlineSpan, 9, 15, Spanned.SPAN_MARK_MARK)//设置span文字颜色val foregroundColorSpan ForegroundColorSpan(Color.parseColor(#0099FF))builder.setSpan(foregroundColorSpan, 9, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)//设置可点击tv.movementMethod LinkMovementMethod.getInstance()tv.setText(builder)
}class NoUnderlineSpan : UnderlineSpan() {override fun updateDrawState(ds: TextPaint) {ds.color ds.linkColords.isUnderlineText false}
}用起来还是比较麻烦的就像上面的代码只是一个 span 就写了三个 setSpan如果需要使用 Span 的地方比较多这些代码看起来实在是不够优雅。有没有更优雅方式呢答案就是 DSL上面的代码最终通过 DSL 封装后如下tvTestDsl.buildSpannableString {addText(我已详细阅读并同意)addText(《隐私政策》){setColor(#0099FF)onClick(false) {//do some thing}}
}他们的显示效果是完全一致的无疑 DSL 的方式更加优雅对于调用者而言也更加方便。实现思路当我有用 DSL 封装 Spannable 这个想法时我首先写的是我应该如何去使用它当时我在纸上胡乱的写下了上面的那段代码。1. 它应该是 TextView的一个扩展函数。2. 它的内部是 DSL 风格的代码。3. 它的每段文字都有设置颜色 点击事件的函数。所以就有了如下的两个接口与扩展函数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()//通过实现类返回SpannableStringBuildertext spanStringBuilderImpl.build()
}上一篇文章我们说了 在 DSL 风格的函数中其参数应当是某个接口或者他的实现类的扩展函数这样我们相当于通过接口来限定了在 DSL 中可调用的函数。上一篇中使用的是实现类本文中使用的是接口原因很简单上文是扩展原有接口变成 DSL 风格本文是直接从无至有实现的 DSL 风格。实现相应接口其实对于像我这样初次接触DSL 的新手而言思路是最难的有了接口有了 DSL 层级剩下的就是相对简单的实现了。直接看代码class DslSpannableStringBuilderImpl : DslSpannableStringBuilder {private val builder SpannableStringBuilder()//记录上次添加文字后最后的索引值var lastIndex: Int 0var isClickable falseoverride fun addText(text: String, method: (DslSpanBuilder.() - Unit)?) {val start lastIndexbuilder.append(text)lastIndex text.lengthval 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? nullvar onClickSpan: ClickableSpan? nullvar useUnderLine trueoverride 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.linkColords.isUnderlineText false}
}总结想要使用 DSL 离不开接口与扩展函数需要先创建想要在 DSL 中使用的函数的接口然后声明函数参数为该接口的扩展函数。如果 DSL 中存在像我这样的嵌套那么就需要为这个嵌套再创建一个用于嵌套调用的接口本文的嵌套是故意为之使用单个接口传参也可以实现这样的效果。