Kotlin DSL

Kotlin DSL

DSL 总体来说是非常简洁的,在使用 Android 开发 java 的过程中,貌似么有使用过 DSL之类的写法,而在 kotlin 中,使用最多的 DSL 就是 anko

根据 anko 的官方文档说明,使用 DSL 有以下好处(也就是使用 xml 的坏处):

By default, UI in Android is written using XML. That is inconvenient in the following ways:

  • It is not typesafe 非类型安全的,也即是 findView 的时候,需要注意类型
  • It is not null-safe 可能为空的
  • It forces you to write almost the same code for every layout you make layout 布局里面存在大量的共同代码,复用率较低
  • XML is parsed on the device wasting CPU time and battery xml 解析消耗额外的 cpu 和时间
  • Most of all, it allows no code reuse 代码不存在复用关系

同样对比传统的 硬编码编写UI 来说,anko 也存在吸引点,就是代码简洁。

例如,传统的硬编码编写 UI 如下:

val act = this
val layout = LinearLayout(act)
layout.orientation = LinearLayout.VERTICAL
val name = EditText(act)
val button = Button(act)
button.text = "Say Hello"
button.setOnClickListener {
    Toast.makeText(act, "Hello, ${name.text}!", Toast.LENGTH_SHORT).show()
}
layout.addView(name)
layout.addView(button)

但是如果使用 anko-layout:

verticalLayout {
    val name = editText()
    button("Say Hello") {
        onClick { toast("Hello, ${name.text}!") }
    }
}
anko-layout 原理

简单来说,anko-layout 的原理就是扩展函数以及DSL,例如在 AnkoLayoutActivity.kt 中有如下代码:

class AnkoLayoutActivity:Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
    fun initLayout(){
        verticalLayout {
            backgroundColor = Color.parseColor("#eeeeee")
        }
    }
}

实际上使用的是 Activity 的一个扩展函数,如下:

inline fun Activity.verticalLayout(theme: Int = 0, init: (@AnkoViewDslMarker _LinearLayout).() -> Unit): LinearLayout {
    return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}

后面的操作,实际上就是帮你创建一个 LinearLayout ,这个 LinearLayout 也是特殊的 LinearLayout,增加了对应的扩展方法,如下:

open class _LinearLayout(ctx: Context): LinearLayout(ctx) {

    inline fun <T: View> T.lparams(
            c: Context?,
            attrs: AttributeSet?,
            init: LinearLayout.LayoutParams.() -> Unit
    ): T {
        val layoutParams = LinearLayout.LayoutParams(c!!, attrs!!)
        layoutParams.init()
        this@lparams.layoutParams = layoutParams
        return this
    }
    ......
}

整体流程图如下:

(图片来源网络)

View的创建一层一层的传递下去,其中共有三种情况,即创建View的三种上下文:

  • Activity,在Activity的onCreate中调用,判断到最后通过setContentView来填充整个布局
  • Context & Fragment,在其他场景下创建的View,可直接通过UI { } 域生成相应的View
  • AnkoContext,在自定义AnkoComponent中的createView方法中定义

也就意味着,你可以在 Activity,Fragment,或者自定义 ViewGroup 中使用 Anko-layout.

原生DSL

anko 支持 DSL 实现原理有两个要素:

  1. Lambdas express
  2. Function-literals-with-receiver

我们以一个案例讲解这里的用法:

inline fun Activity.verticalLayout(theme: Int = 0, init: (@AnkoViewDslMarker _LinearLayout).() -> Unit): LinearLayout {
    return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}

首先,在Activity 的扩展函数 verticalLayout() 里面,传入一个 lambdas 作为参数,init: (@AnkoViewDslMarker _LinearLayout).() -> Unit),然后这个命名为 init 的函数,是一个带接受者的函数,也就是 _LinearLayout.() -> Unit,其接受者类型为 _LinearLayout,这个类继承自 LinearLayout。

上述代码可以简化成这样:

fun Context.createLinearLayout(init:LinearLayout.() ->Unit):LinearLayout{
    val layout = LinearLayout(this)
    layout.init()
    return  layout
}

所以你在某种 Context 内调用这个 createLinearLayout 的时候,就可以直接传入一个 Lambdas 表达式,其this 指向 LinearLayout,所以你调用该方法的时候,如下:

//anko 的写法
verticalLayout {
    backgroundColor = Color.parseColor("#eeeeee")
}
//我们自己的写法
createLinearLayout { 
    backgroundColor = Color.parseColor("#eeeeee")
}
DslMarker

其次Anko 为了提高 DSL 的效率,还是使用了 @DslMarker 注解:

//Ui.kt 中
@DslMarker
@Target(AnnotationTarget.TYPE)
annotation class AnkoViewDslMarker
//Annotatios.kt 中
@Target(ANNOTATION_CLASS)
@Retention(BINARY)
@MustBeDocumented
@SinceKotlin("1.1")
public annotation class DslMarker

根据kotlin 官方文档的解释:

The general rule:

  • an implicit receiver may belong to a DSL @X if marked with a corresponding DSL marker annotation

  • two implicit receivers of the same DSL are not accessible in the same scope

  • the closest one wins

  • other available receivers are resolved as usual, but if the resulting resolved call binds to such a receiver, it’s a compilation error

    这里规定了DSL 的一些有意义的规范,例如DSL 块必须有接收者,其次不允许在一个代码块,存在两个相同的接收者,只要靠近代码块那个,才会生效。

简单来说,DslMarker 是为了控制作用范围,例如以下的情况:

html {
    head {
        head { } // 错误:外部接收者的成员
    }
    // ……
}

也就是说,第二个 head 代码块的接受者实际上应该是 html,但是在这种情况下,明显就不是了,所以使用 DslMarker,编译器会提示这种错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值