网站模板 兼容ie8,电子商务网站建设一般流程,成都私人定制旅游公司排名,制作影视视频的软件概念本源
在界面程序开发中#xff0c;有两个非常典型的编程范式#xff1a;命令式编程和声明式编程。命令式编程是指通过编写一系列命令来描述程序的运行逻辑#xff0c;而声明式编程则是通过编写一系列声明来描述程序的状态。在命令式编程中#xff0c;程序员需要关心程…
概念本源
在界面程序开发中有两个非常典型的编程范式命令式编程和声明式编程。命令式编程是指通过编写一系列命令来描述程序的运行逻辑而声明式编程则是通过编写一系列声明来描述程序的状态。在命令式编程中程序员需要关心程序的执行过程而在声明式编程中程序员只需要关心程序的状态。在界面开发中声明式编程的优势尤为明显因为界面开发的本质就是描述界面的状态。
命令式编程范式
举个简单的例子图形界面通常需要考虑的问题是把一堆界面元素组成合理的树状结构在命令式编程中我们的做法看起来是下面这样的伪代码
val root Container()
val label Label()
val button Button()
val panel Panel()panel.add(label)
panel.add(button)
root.add(panel)root.show()这很合理这个命令式编程的代码描述了我们创建一系列对象包括容器、面板、界面元素然后通过结构调整他们的相互关系构成界面。在完成构造之后我们调用 root.show() 来显示这个界面。这个过程中我们需要关心的是对象的创建、对象的关系、对象的显示这是一个过程性的描述。
声明式编程范式
其实最常见的声明式编程范式就是 HTMLHTML 是一种标记语言它的本质是一种声明式的描述我们通过 HTML 来描述界面的结构而不是描述界面的构造过程。下面是一个简单的 HTML 代码片段
!DOCTYPE htmlhtml
headtitleMy First HTML Page/title
/head
bodyh1Hello, World!/h1
/body
/html好多图形界面开发的程序Qt或者WPF都采用XML来描述界面这样的方式也是声明式的。在这种方式中我们只需要关心界面的结构而不需要关心界面的构造过程。这种方式的优势在于我们可以更加专注于界面的结构而不需要关心界面的构造过程。
Jetpack Compose的声明式界面开发
在Jetpack Compose中提出了一个概念就是可组合的声明式界面开发。
比如描述一列标签构成的界面我们可以这样写
Composable
fun Greeting(name: String) {Text(text Hello $name!)
}Composable
fun MyScreenContent(names: ListString listOf(Android, there)) {Column {for (name in names) {Greeting(name name)Divider(color Color.Black)}}
}这里通过Composable注解我们定义了一个可组合的函数Greeting这个函数接受一个字符串参数然后返回一个Text组件。然后我们定义了一个MyScreenContent函数这个函数接受一个字符串列表参数然后返回一个Column组件这个Column组件包含了一系列的Greeting组件和Divider组件。这样我们就完成了一个简单的界面的描述。
深入一下
这样的方式就好像是XML这样的结构化文档但是有是能够运行的代码。非常有意思最好玩的是还能通过循环、判断来动态生成界面这样的方式非常灵活而且非常容易理解。
那么这个玩意是如何实现的呢
在前面Kotlin旋风之旅中我们提到了Kotlin的DSL这个DSL就是Jetpack Compose的核心。
Jetpack Compose的核心思想就是通过实现一种专门用于描述界面的DSL开发人员通过这套DSL来描述和生成界面。
下面我们也试着用Kotlin的DSL来实现一个简单的DSL通过这个DSL实现过程对Jetpack Compose的实现原理有一个祛魅的过程神秘感不那么强调试的过程也会更加容易。
简化家族树DSL
我们要实现的是一种单体繁殖、类人、外星生物也称为Person的家族树DSL这个DSL的结构如下
fun main() {Person(Alice, 80) {Children {name Tomage 50Children {name Jerryage 25Children(Tom, 2)}Children {name Yanage 15}// 年龄写错了改一下age 53}Children(name Tim, age 40) {Children(name Jerry, age 5)Children(name Alex, age 15)}// 调用输出函数打印家族树print()}
}这个描述的家族树大概是 |___Name: Bob, Age: 60|___Name: Tom, Age: 50|___Name: Jerry, Age: 25|___Name: Tom, Age: 2|___Name: Yan, Age: 15|___Name: Tim, Age: 40|___Name: Jerry, Age: 5|___Name: Alex, Age: 15可以看到这个代码有几个特点
通过Person函数来描述一个人这个函数接受一个名字和年龄然后通过Children函数来描述这个人的孩子。名字和年龄可以省略也可以通过name和age参数来指定。描述的过程中如果需要修改也能通过name和age参数来修改。能够调用print函数来打印自己的家族树。
这个DSL看起来非常简单其实非常强大。这样就能够把一个家族树描述成跟其天然结构非常接近的、合法的Kotlin代码。
实现DSL
这个东西是怎么实现的呢现给出完整代码
class PersonImpl(n: String , a: Int 0) {var name: String nvar age: Int aprivate fun nBlank(indent: Int) .repeat(indent)fun print(indent: Int 0) {print(${nBlank(indent)}|___)print(Name: $name, )println(Age: $age)for (child in children) {child.print(indent 2)}}private val children mutableListOfPersonImpl()fun addChildren(name: String, age: Int, block: PersonImpl.() - Unit {}) {val child PersonImpl()child.name namechild.age agechild.block()children.add(child)}operator fun invoke(block: PersonImpl.() - Unit {}): PersonImpl {block()return this}}fun Person(name: String , age: Int 0, block: PersonImpl.() - Unit {}) PersonImpl(name, age)(block)fun PersonImpl.Children(name: String , age: Int 0, block: PersonImpl.() - Unit {}) addChildren(name, age, block)代码解析
上面的调用Person的方式要能实现就需要定义一个函数它包括三个参数并且最后一个参数必须是一个能够接受某个类型的函数。
所以fun Person(name: String , age: Int 0, block: PersonImpl.() - Unit {}) PersonImpl(name, age)(block)符合这个要求这也是一个Kotlin的语法糖单行函数的函数定义。
同时这个函数申明还省略了返回值类型这是因为Kotlin的类型推导能力很强编译器能够根据函数体的返回值类型推导出函数的返回值类型。
写成完整的函数形式并且把构造对象和调用函数分开来写是这样的。
fun Person(name: String , age: Int 0, block: PersonImp.() - Unit{}) {val p PersonImp(name, age)p(block)
}这个函数提供了调用Person(){}的方式在大括号里面的代码针对一个PersonImpl实例进行操作这种方式称为接受者函数字面值。这个功能的实现我猜要依赖于扩展函数的特性相当于零时定义一个对象的扩展函数并且在函数体内部可以直接访问这个对象的属性和方法。
当然要能够想函数一样调用这个新建的对象就需要在PersonImpl类中定义一个invoke操作符函数这个函数的返回值是PersonImpl这样就能够实现Person(){}的调用方式。
接下来就是Children函数这个函数的作用是为一个PersonImpl对象添加一个孩子这个函数的实现也是类似的通过addChildren函数来实现。
fun PersonImpl.Children(name: String , age: Int 0, block: PersonImpl.() - Unit {}) addChildren(name, age, block)这个实现了一个扩展函数这个函数因此只能在PersonImpl对象上调用当然前面那个接受者函数的代码block里面所有的调用都是针对PersonImpl对象的。
其他普通的构造函数、默认参数、属性、方法等等都是普通的Kotlin代码没有什么特别的。
总结
在深入进行Jetpack Compose的学习之前我们先通过一个简单的DSL实现了解了Jetpack Compose的核心思想通过声明式的DSL来描述界面。这样的方式非常灵活而且非常容易理解也非常容易调试。通过这样的方式我们可以更加专注于界面的结构而不需要关心界面的构造过程。
这个实现的过程中两个语法糖要自己在大脑里反复转换最后一个参数是匿名函数则可以移到括号外面接受者匿名函数相当是临时定义一个扩展函数。
有一点点绕但是多改改代码也能够理解。
接下来就要开始真正的Jetpack Compose的学习之旅了。