-
- DSL构建布局
-
前言
-
一、What’s the DSL?
-
二、初探
-
- 1.DSL常见的应用
-
三.koltin中的DSL
-
-
1.扩展函数
-
2.带接收者的 Lambda 表达式
-
3.用DSL构建布局
-
-
总结
-
-
一.对于DSL构建的布局
-
二. Compose
-
[](
)前言
=================================================================
入职新公司后发现kotlin的语法知识跟不上,因为公司开发团队的习惯问题,原先的公司还是java+kt混编开发的模式,许多同事基本不用kt,一些不常规的语法应用很少接触使用,还存在使用错误的情况,所以现在要补一些ktolin语法使用的知识,而DSL就是第一个点
[](
)一、What’s the DSL?
================================================================================
先上一小段可爱的代码:
ConstraintLayout {
layout_width = wrap_content
layout_height = wrap_content
background_res = R.drawable.ic1
ImageView {
layout_width = 22
layout_height = 22
top_toTopOf = parent_id
end_toEndOf = parent_id
margin_top = 9
margin_end = 5
padding = 5
scaleType = scale_fit_xy
src = R.drawable.ic2
}
TextView {
layout_id = "tv1"
layout_width = wrap_content
layout_height = wrap_content
textColor = "#ff3f4658"
textSize = 16f
textRes = R.string.tv1
center_horizontal = true
top_toBottomOf = "iv"
margin_top = 3
}
TextView {
layout_id = "tv2"
layout_width = wrap_content
layout_height = wrap_content
textColor = "#ffffff"
textSize = 12f
center_horizontal = true
top_toBottomOf = "tv1"
margin_top = 8
text = "deo"
}
}
土包子的我第一次看到这样的定义布局方式并没有感觉有啥新奇,不就是动态设置控件嘛,并没有什么高级的地方,反而觉得不如XML直观且无法预览,但一仔细看就不对了,按照常规的写法应该是从父布局开始逐个初始化对象,然后通过layouparmars设置布局属性添加到相应容器中,这种写法是什么情况,貌似直接嵌套还直接定义了控件属性,和xml的语法很像但貌似更简便(XML也可以看做是DSL)。此时只有一个字可以形容
通过偷偷摸摸的百度后知道这就是传说中的DSL构建的布局方式,这个东西是有了解过的,但没深入的看过(看完就忘),kt的部分语法感觉过于难以阅读所以一直抗拒(其实是菜看不懂),靠着apply、let、with苟延残喘的使用着,还感觉自己kt用的很nice。
这么陌生且高大上的词汇看的第一眼就感觉很难,自然反应的Google一下:
给出的解释就是*领域专用语言***。 词汇太专业,依然不知道是啥。。。查的时候还看到另一个词:[GPL](
) ,有必要了解一下,General Purpose Language翻译过来就是通用语言,如Java、OC、C等等,这个好理解脑子一下就知道是啥了。这些语言使用很广泛,像java能 写android也能写后端,业务代码都是用这些语言写的。
对比一下GPL,DSL的针对性更强,只适用于特定的功能方向,就像文言文和白话文,一个用于作文记事,一个用于交际用语,举个可能不恰当的栗子,墓志铭就有特定的格式,第一他只能写给死人,第二有固定的格式,其他的像啥三言、五言、七言的,不是很准确但就是那个意思吧。
[](
)二、初探
===================================================================
[](
)1.DSL常见的应用
简单的了解过后感觉DSL更像是一种自己构建出来快捷的语法格式或者说结构,像最上面的布局代码其实最后做的工作应该和普通的写法(控件都最后都要初始化)是一样的,但对于项目来说大量频繁的重写就会省力很多且代码格式更好,当然好处不止于此。很多东西都见过也用过只是不知道原来这么D伊奥。
第一个众所周知的HTML就是典型的DSL,首先她不算是编程语言,它定义了特殊的网页结构标示不同的含义,还一个特点他不能像java那样写出复杂的逻辑,它们都是命令式的,瞬间清醒,此时DSL和GPL的区别通透了。
其他的一些我们接触过的包括Gradle、SQL,按照上面的特点,SQL语句能写逻辑吗?Gradle语法能写复杂逻辑吗?
他们的语句还有些共同点,仔细的回忆一下SQL、Gradle语句,是不是容易忘记,容易不认识,容易写错,可能一年没写过一条SQL的人大把抓,但他们都比较有特定的结构且只能用于实现特定功能,因为语法和常规的语言编写格式不同所以容易忘记。SQL编写语句管理数据,Gradle配置项目用于构建,HTML用于显示网页文本内容,而不像Java或者其他编程语言一样,没有特定的限制,想在哪用在哪用。
[](
)三.koltin中的DSL
============================================================================
要实现开头的布局方式需要怎么做呢?因为SDK也没定义这种规范,kt也没有直接提供,所以只能内部创造,手动满足。
在kotlin中实现DSL构建要靠两样东西:
.扩展函数;
.带接收者的 Lambda 表达式;
先说扩展函数,这个还是比较简单的。
[](
)1.扩展函数
fun String.customOperation(append:String) :String{
return this+append
}
上面写了个String的扩展函数,自定义了一个操作,对接受者拼接了append字符串,在其他地方可以把customOperation当做系统的api直接调用。扩展函数的作用也一目鸟然,可以封装频率使用高的操作代码,可以像使用原生API一样使用扩展方法。
再看一下字节码:
// access flags 0x19
public final static customOperation(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
@Lorg/jetbrains/annotations/NotNull;() // invisible
// annotable parameter count: 2 (visible)
// annotable parameter count: 2 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1
L0
ALOAD 0
LDC "$this$customOperation"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
ALOAD 1
LDC "append"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 11 L1
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN
L2
LOCALVARIABLE $this$customOperation Ljava/lang/String; L0 L2 0
LOCALVARIABLE append Ljava/lang/String; L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
@Lkotlin/Metadata;(mv={1, 4, 0}, bv={1, 0, 3}, k=2, d1={"\u0000\n\n\u0000\n\u0002\u0010\u000e\n\u0002\u0008\u0002\u001a\u0012\u0010\u0000\u001a\u00020\u0001*\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0001\u00a8\u0006\u0003"}, d2={"customOperation", "", "append", "My_Application.app"})
// compiled from: Extends.kt
}
static 圈一下,一目了然,扩展方法被编译成静态方法,(Ljava/lang/String;Ljava/lang/String;) 圈一下,咦?怎么两个String呢?
[](
)2.带接收者的 Lambda 表达式
带接受者的Lambda是啥样子,首先函数式编程Lambda是kt的一大特色(简洁避免语法噪音),但高阶函数看起来就很累,阅读性感觉不太友好,过于简化(主要还是菜),rxjava、协程一起用的时候就更懵逼了,lowB的代码不香吗?
要知道啥是高阶函数,简单无脑的理解就是函数嵌套函数,在kt中lambda是可以当做参数进行传递的
fun createString(build:(StringBuilder)->Unit):String {
val stringBuilder=StringBuilder()
build(stringBuilder)
return stringBuilder.toString()
}
如果想要通过StringBuild去创建一个自定义的拼接字符串可以用上面的方法,比如:
这就是lambda作为参数传递的一个简单栗子,但是在调用createString 的时候,在表达式中需要显示的调用it,这个it就是StringBuilder,而这个表达式的调用者是期望或者我们已经指定了就是一个StringBuilder,所以可以避免这种显示调用的现象,在声明表达式的时候为它指定调用者,所以修改createString传入的表达式:
fun createString(build:StringBuilder.()->Unit):String {
val stringBuilder=StringBuilder()
build(stringBuilder)
return stringBuilder.toString()
}
这个时候可以看到调用函数的地方报错了
可以看到编译的提示,表达式的参数提示由it变成了this,这个时候我们直接可以使用StringBuilder的方法而不需要显示的使用it
在上面的方法中 build:StringBuilder.()->Unit就是一个带接受者的Lambda 表达式,它的声明语法格式是: