当前位置: 首页 > news >正文

003-Kotlin界面开发之声明式编程范式

在这里插入图片描述

概念本源

在界面程序开发中,有两个非常典型的编程范式:命令式编程和声明式编程。命令式编程是指通过编写一系列命令来描述程序的运行逻辑,而声明式编程则是通过编写一系列声明来描述程序的状态。在命令式编程中,程序员需要关心程序的执行过程,而在声明式编程中,程序员只需要关心程序的状态。在界面开发中,声明式编程的优势尤为明显,因为界面开发的本质就是描述界面的状态。

命令式编程范式

举个简单的例子,图形界面通常需要考虑的问题是把一堆界面元素组成合理的树状结构,在命令式编程中,我们的做法看起来是下面这样的伪代码:

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() 来显示这个界面。这个过程中,我们需要关心的是对象的创建、对象的关系、对象的显示,这是一个过程性的描述。

声明式编程范式

其实最常见的声明式编程范式就是 HTML,HTML 是一种标记语言,它的本质是一种声明式的描述,我们通过 HTML 来描述界面的结构,而不是描述界面的构造过程。下面是一个简单的 HTML 代码片段:

<!DOCTYPE html><html>
<head><title>My First HTML Page</title>
</head>
<body><h1>Hello, World!</h1>
</body>
</html>

好多图形界面开发的程序,Qt或者WPF都采用XML来描述界面,这样的方式也是声明式的。在这种方式中,我们只需要关心界面的结构,而不需要关心界面的构造过程。这种方式的优势在于,我们可以更加专注于界面的结构,而不需要关心界面的构造过程。

Jetpack Compose的声明式界面开发

在Jetpack Compose中,提出了一个概念就是可组合的声明式界面开发。

比如,描述一列标签构成的界面,我们可以这样写:

@Composable
fun Greeting(name: String) {Text(text = "Hello $name!")
}@Composable
fun MyScreenContent(names: List<String> = 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 = "Tom"age = 50Children {name = "Jerry"age = 25Children("Tom", 2)}Children {name = "Yan"age = 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

可以看到这个代码有几个特点:

  1. 通过Person函数来描述一个人,这个函数接受一个名字和年龄,然后通过Children函数来描述这个人的孩子。
  2. 名字和年龄可以省略,也可以通过nameage参数来指定。
  3. 描述的过程中,如果需要修改,也能通过nameage参数来修改。
  4. 能够调用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 = mutableListOf<PersonImpl>()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的学习之旅了。

相关文章:

003-Kotlin界面开发之声明式编程范式

概念本源 在界面程序开发中&#xff0c;有两个非常典型的编程范式&#xff1a;命令式编程和声明式编程。命令式编程是指通过编写一系列命令来描述程序的运行逻辑&#xff0c;而声明式编程则是通过编写一系列声明来描述程序的状态。在命令式编程中&#xff0c;程序员需要关心程…...

QT pro项目工程的条件编译

QT pro项目工程的条件编译 前言 项目场景&#xff1a;项目中用到同一型号两个相机&#xff0c;同时导入两个版本有冲突&#xff0c;编译不通过&#xff0c; 故从编译就区分相机导入调用&#xff0c;使用宏区分 一、定义宏 在pro文件中定义宏&#xff1a; DEFINES USE_Cam…...

深度学习之经典网络-AlexNet详解

AlexNet 是一种经典的卷积神经网络&#xff08;CNN&#xff09;架构&#xff0c;在 2012 年的 ImageNet 大规模视觉识别挑战赛&#xff08;ILSVRC&#xff09;中表现优异&#xff0c;将 CNN 引入深度学习的新时代。AlexNet 的设计在多方面改进了卷积神经网络的架构&#xff0c;…...

部署Prometheus、Grafana、Zipkin、Kiali监控度量Istio

1. 模块简介 Prometheus 是一个开源的监控系统和时间序列数据库。Istio 使用 Prometheus 来记录指标&#xff0c;跟踪 Istio 和网格中的应用程序的健康状况。Grafana 是一个用于分析和监控的开放平台。Grafana 可以连接到各种数据源&#xff0c;并使用图形、表格、热图等将数据…...

结合 Spring Boot Native 和 Spring Boot 构建高性能服务器架构

随着云计算和微服务架构的普及&#xff0c;开发者们不断寻求提高应用性能和用户体验的解决方案。Spring Boot Native 的出现&#xff0c;利用 GraalVM 的原生映像特性&#xff0c;使得 Java 应用的启动速度和资源占用得到了显著改善。本文将深入探讨如何将前端应用使用 Spring …...

ArcGIS影像调色(三原色)三原色调整

本期主要介绍ArcGIS影像调色&#xff08;三原色&#xff09; ArcGIS影像调色&#xff08;三原色&#xff09;&#xff0c;对比度、亮度、gamma。红绿蓝三原色调整。 视频学习 ArcGIS影像调色&#xff08;三原色&#xff09;...

SQLite从入门到精通面试题及参考答案

目录 SQLite 是什么? SQLite 的优点有哪些? 轻量级与易于部署 零配置和低维护成本 良好的兼容性和跨平台性 高性能和可靠性 SQLite 的局限性有哪些? 并发处理能力有限 缺乏用户管理和权限控制功能 有限的扩展性 有限的网络支持 SQLite 和其他数据库系统(如 MyS…...

【C/C++】字符/字符串函数(0)(补充)——由ctype.h提供

零.导言 除了字符分类函数&#xff0c;字符转换函数也是一类字符/字符串函数。 C语言提供了两种字符转换函数&#xff0c;分别是 toupper &#xff0c; tolower。 一.什么是字符转换函数&#xff1f; 顾名思义&#xff0c;即转换字符的函数&#xff0c;如大写字母转小写字母&am…...

Git 的特殊配置文件

文章目录 1.前言2.Git 标准配置文件.gitignore作用格式示例 .gitattributes作用格式示例 .gitmodules作用格式示例 .gitconfig作用格式示例 3.非 Git 标准约定文件.gitkeep简介示例 .gitacls作用格式示例 参考文献 1.前言 Git 是一个强大的版本控制系统&#xff0c;它使用多个…...

数据的表现形式(1)

数据的表现形式 西文字符编码 ASCII码&#xff08;美国信息交换标准码&#xff09; 计算机内部用一个字节&#xff08;8位二进制&#xff09;&#xff0c;来存放一个7位ASCII码&#xff0c;最高位为“0”&#xff0c;共 可以表示128个不同字符 ASCII码中&#xff0c;0是48&…...

《高频电子线路》—— 调幅

文章内容来源于【中国大学MOOC 华中科技大学通信&#xff08;高频&#xff09;电子线路精品公开课】&#xff0c;此篇文章仅作为笔记分享。 调幅 普通调幅 AM 普通调幅&#xff0c;也属于线性调制&#xff0c;需要了解其时域和频域。 时域&#xff08;表达式&#xff09; vΩ…...

ubuntu22.04安装ROS2Humble

参考链接 Ubuntu22.04——ROS2安装以及小海龟画圆 Ubuntu 22.04 安装 ros noetic Slam_in_autonomous_driving(一) 环境配置...

软中端,硬中断(学习笔记)

/proc/softirqs 提供了软中断的运行情况&#xff1b; /proc/interrupts 提供了硬中断的运行情况。 以下图片展示的是软中断内容&#xff1a; 在查看 /proc/softirqs 文件内容时&#xff0c;你要特别注意以下这两点。 第一&#xff0c;要注意软中断的类型&#xff0c;也就是这…...

scIDST:弱监督学习推断单细胞转录组数据中的疾病进展阶段

背景&#xff1a;患者来源组织中的单个细胞&#xff0c;每个都处于不同的病理阶段&#xff0c;因此这种细胞变异性阻碍了随后的差异基因表达分析。 结果&#xff1a;为了克服这种异质性问题&#xff0c;作者提出了一种新的深度学习方法&#xff0c;scIDST&#xff0c;该方法可以…...

Linux 下执行定时任务之 Systemd Timers

不知道 ECS 因为什么缘故&#xff0c;上面安装的 MySQL 服务老是不定期挂掉&#xff0c;本来想通过 Linux 得 Cron 配置个半小时的定时检测任务&#xff0c;结果一直没有执行&#xff0c;因此又尝试使用了 Systemd Timers 进行了重新配置&#xff0c;简要做个记录。 Systemd Ti…...

flutter 专题二 Flutter状态管理之Riverpod 0.8.4

一 、flutter 有哪些状态管理方式 Flutter的状态管理方式有很多&#xff0c;Redux、 Bloc、 MobX、Provider等等。单单一个Provider&#xff0c;我也见到了各种组合&#xff0c;例如ChangeNotifier Provider / StateNotifier Provider&#xff08; freezed&#xff09;。各…...

【Linux】从零开始使用多路转接IO --- poll

碌碌无为&#xff0c;则余生太长&#xff1b; 欲有所为&#xff0c;则人生苦短。 --- 中岛敦 《山月记》--- 从零开始使用多路转接IO 1 前言1 poll接口介绍3 代码编写4 总结 1 前言 上一篇文章我们学习了多路转接中的Select&#xff0c;其操作很简单&#xff0c;但有一些缺…...

Docker配置宿主机目录和网络映射

容器挂载宿主机目录 在Docker中&#xff0c;你可以通过-v或--volume选项将宿主机的目录挂载到容器中。这可以让你在容器和宿主机之间共享文件。 例如&#xff0c;如果你想将宿主机的/home/user/data目录挂载到容器的/data目录&#xff0c;你可以使用以下命令&#xff1a; do…...

第十七课 component组件解析

component组件解析 component组件的写法在众多组件写法中算是比较简单的&#xff0c;component组件结构组成如下&#xff1a; 1&#xff09;组件名 2&#xff09;组件模板 3&#xff09;利用Vue对象进行生成 基础示例&#xff1a; <div id"app"><test>…...

求余和求模是不是一样的,就要看看计算机中的 fix 和 floor 区别

在计算机中&#xff0c;fix和floor是两个不同的取整函数&#xff0c;它们各自有不同的取整规则。以下是fix和floor的详细区别&#xff1a; 一、定义与功能 fix函数 定义&#xff1a;fix函数是朝零方向取整的函数&#xff0c;即它会返回小于或等于&#xff08;对于正数&#xf…...

穿越机老鸟踩坑实录:MPU6000传感器在F4飞控上的IMU方向“玄学”配置

穿越机IMU方向配置实战&#xff1a;从MPU6000异常自旋到飞控底层校准 当你的穿越机在通电瞬间像被无形大手狠狠抽了一记耳光般疯狂自旋&#xff0c;而Betaflight地面站里陀螺仪数据却显示"一切正常"时&#xff0c;这往往意味着你正遭遇IMU方向配置的"量子纠缠态…...

告别ET1100?聊聊AX58100这颗高性价比EtherCAT从站芯片的升级体验

告别ET1100&#xff1f;AX58100高性价比EtherCAT从站芯片的工业升级实战 当工业设备制造商面临从传统控制架构向实时以太网迁移时&#xff0c;EtherCAT从站芯片的选型往往成为关键转折点。十年前&#xff0c;ET1100凭借其稳定的性能和相对友好的开发门槛&#xff0c;成为许多工…...

跨越平台限制:如何用WorkshopDL免费获取Steam创意工坊模组

跨越平台限制&#xff1a;如何用WorkshopDL免费获取Steam创意工坊模组 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 还在为Epic Games或GOG平台无法访问Steam创意工坊而烦恼吗…...

开源机械爪控制库:从PID算法到ROS集成的全栈开发指南

1. 项目概述&#xff1a;一个开源的机械爪设计与控制库最近在机器人硬件开发的圈子里&#xff0c;开源项目“MeyerZhou/openclaw”引起了不少创客和机器人爱好者的注意。简单来说&#xff0c;这是一个专注于机械爪&#xff08;或称机械手、夹爪&#xff09;设计与控制的代码库和…...

Seraphine:英雄联盟智能BP助手与战绩查询工具完整指南

Seraphine&#xff1a;英雄联盟智能BP助手与战绩查询工具完整指南 【免费下载链接】Seraphine 英雄联盟战绩查询工具 项目地址: https://gitcode.com/gh_mirrors/se/Seraphine 在英雄联盟的对局中&#xff0c;BP&#xff08;禁选英雄&#xff09;阶段往往是决定胜负的关…...

GURU-Ai:面向开发者的AI命令行工具集,提升代码理解与运维效率

1. 项目概述&#xff1a;一个面向开发者的AI助手工具集最近在GitHub上看到一个挺有意思的项目&#xff0c;叫“Guru322/GURU-Ai”。光看名字&#xff0c;你可能会觉得这又是一个大而全的AI模型或者聊天机器人&#xff0c;但点进去仔细研究后&#xff0c;我发现它的定位其实非常…...

STM8硬件IIC驱动BNO055传感器避坑指南(附完整代码)

STM8硬件IIC驱动BNO055传感器实战解析与优化 BNO055作为一款集成了9轴传感器融合算法的智能芯片&#xff0c;能够直接输出姿态角数据&#xff0c;极大简化了嵌入式系统中姿态解算的复杂度。然而在实际应用中&#xff0c;许多开发者发现使用STM32等常见MCU的模拟IIC接口难以稳定…...

AI驱动的Web可访问性审查:LLM如何成为你的自动化无障碍专家

1. 项目概述&#xff1a;一个为AI智能体而生&#xff0c;却意外照亮了所有人的可访问性审查工具 最近在折腾AI智能体&#xff08;AI Agent&#xff09;的开发&#xff0c;一个老问题又浮上水面&#xff1a;怎么确保我造出来的这个“数字员工”&#xff0c;能真正服务好所有人&…...

Biomni项目解析:大语言模型与生物医学知识图谱融合实践

1. 项目概述&#xff1a;当大语言模型遇见生物医学知识图谱最近在探索如何让大语言模型&#xff08;LLM&#xff09;在专业领域&#xff0c;特别是生物医学这种信息密集、关系复杂的领域&#xff0c;变得更“靠谱”一点。相信很多同行都遇到过类似的问题&#xff1a;直接问Chat…...

IE11富文本兼容——政务系统前端的深渊

IE11富文本兼容——政务系统前端的深渊 背景&#xff1a;为什么还有 IE11 系统要求支持 IE11。 为什么不是 Chrome&#xff1f; 办公电脑全是 Windows 7 IE11单位统一采购&#xff0c;不能随便装浏览器部分内部网站只支持 IE&#xff08;ActiveX&#xff09; 现状&#x…...