Android Kotlin Jetpack Compose UI框架 完全解析

本文已参与好文召集令活动,点击检查:后端、大前端双赛道投稿,2万元奖池等你应战!

(本篇触及内容较多,篇幅较长,建议保藏,静心阅览。)

前语

Q1的时候公司列了个训练方案,部分人作为讲师要上报训练课题。那时候刚从好几个Android项目里抽离出来,正好看到Jetpack发布了新玩意儿——Compose,我被它的快速实时打包给吸引住了,就准备调研一下,所以上报了此次课题。

但是方案总赶不上改变,刚把课题报上去,我就扎入了前端的水深火热之中。从0到1地学习前端,一边学一边做项目,一边做项目一边分享,思考怎么让他人也学会做前端项目,这段时刻,真的酸爽。

跟着时刻推移,之前上报的课题分享也快临近了,这才想起来我还欠一个交代。没办法,自己报的课题,熬夜也要赶出来。

下面,我会从这几个方面来论述这次课题:

  • Compose是什么
  • 怎么高雅地运用Compose
  • 最后,Compose是否值得一试

名词解析:

以下用到的专业术语或许会有出入,为了避免混淆,下面做一个名词解析表:

名词 解析 备注
组件 可以操控页面展现的部分UI的逻辑单元
View 可以展现的UI,并具备自己保护状况的才能
微件 组件,可以操控页面展现的部分UI的逻辑单元

Compose官方文档中,新发明了一个名词——“微件”
微件 可以理解为Android现在用到的各种 View,也可以理解为H5前端里常说的 组件

1 Compose是什么

Jetpack Compose 是用于构建原生界面的新款 Android 工具包。它可简化并加速 Android 上的界面开发。运用更少的代码、强壮的工具和直观的 Kotlin API,快速让运用生动而精彩。

这么一听感觉有点抽象,不知道再讲什么。

我来翻译一下:

Jetpack Compose 是一款依据Kotlin API,从头界说Android布局的一套结构,它可以更快速地完结Android原生运用。节省开发时长,削减包体积,进步运用功能。

节省开发时长,削减包体积,进步运用功能。 这个听起来很诱人,咱们来看看它的效果怎么。

1.1 Android Studio 对Compose 的支撑

(本节要感谢 仍然范特稀西 供给的实践数据)

强壮的预览

这一功能依据新版Android Studio 对Compose 的支撑。

新版的Android Studio Arctic Fox(现在仍是Canary版别) 中添加了许多新工具来支撑Jetpack Compose新特性,比方:实时文字、动画预览,布局检查等等。

1.1.1 强壮的预览

新的Android Studio 添加了对文字更改实时预览的效果,可以在Preview、模拟器、或许真机上实时预览。

111.gif

1.1.2 动画预览

可以在AndroidStudio内检查、检查或播映动画,还可以逐针播映。

222.gif

1.1.3 布局检查器

Android Studio Arctic Fox 添加了布局监测器对Compose的支撑,可以分析Compose组件的层级。如下所示:

333.gif

1.1.4 交互式预览

在此方式下,你可以与界面组件互动、点击组件,以及检查状况怎么改变。经过这种办法,你可以快速获得有关界面怎么反应的反应,并可快速预览动画。如要启用此方式,只需点击“互动”图标 ,体系即会切换预览方式。

89074-iw717wisn5b.png

如需停止此方式,请点击顶部工具栏中的 Stop Interactive Preview。

以上是AndroidStudio对Compose的支撑,可以说是大手笔了。

1.2 Jetpack Compose 运用前后对比

你以为Compose只是添加了预览功能?那可不是。

从普通运用切换到Compose运用,你的运用速度和功能可以得到大幅进步。

咱们来看一个Google官方改造的运用示例。

1.2.1 APK 尺寸减缩

用户最为关怀的目标,莫过于 APK 巨细。

下面是开启了 资源减缩 的最小化发布版 APK (运用了 R8) 经过 APK Analyzer 所测量的结果:

63402-996raj8mnrr.png

97775-9mv5wydrwu8.png

关于上述数字的说明:

1、运用了 APK Analyzer 陈述的 “APK file size” (而不是下载时的巨细)。
APK 巨细分析

2、在运用了 Compose 后,咱们发现 APK 巨细减缩了 41%,办法数削减了 17%

1.2.2 代码行数

源代码行数尽管不能作为衡量软件好坏的规范,但是可以对比出一个试验在“瘦身”上面做了多大的极力,为调查试验改变供给了一个核算视角。

78697-3u9txjsjll3.png

从图中可以看到,XML 行数大幅削减了 76%再见了,布局文件,以及 styles、theme 等其他的 XML 文件 。

同时,Kotlin 代码的总行数也下降了。

这便是 APK 可以瘦身的很大一部分原因。

1.2.3 构建速度

构建速度是开发者们十分关怀的一项目标。

31273-ry24br610mn.png

这儿需求做一些说明:

“彻底接入 Compose” 运用的是最新版别的 Dagger/Hilt,该版别运用了 Android Gradle Plugin 7.0 中的新 ASM API。而其他版别运用了较旧的 Hilt 版别,其运用了不同的机制,会严重拖慢生成 dex 文件的时刻。

除此之外,Kotlin 编译器与 Compose 编译器插件为咱们所做的作业,如 方位记忆化、细粒度重组 等作业,构建时刻可以 削减 29%, 可以说十分惊人。

2 怎么高雅地运用Compose

上面讲了很多Compose的长处,那么,接下来咱们怎么运用它呢。

2.1 准备

在开端运用Compose之前,你需求具备一下根底。

  • 下载 Android Studio Arctic Fox 或更高版别
  • Kotlin 1.4.32 或更高版别
  • Kotlin 言语运用无障碍

2.2 快速建立Compose

怎么快速建立Compose新项目,以及怎么将旧有项目迁移成Compose项目。

具体实践进程,我在**《用Android 最新UI 结构 「 Compose 」快速构建运用》** 里已经讲得十分具体,这儿不再赘述。

2.3 怎么快速学习Compose

首要,祝贺你看到这儿,这篇文章看完,你就可以上手开发了。

你也可以到官网供给的 【快速上手】 示例教程 学习怎么快速运用Compose根底。

你还可以在YouTube上观看【教育视频】,

或许到gitHub下载【demo集合】,官方供给了很多demo示例,见下方示例图:

98098-yv3otge0svl.png

2.4 Compose编程思想

了解了怎么建立,以及怎么编写demo,最终运用到项目之前,你还需求了解一些必备且重要的Compose根底。

首当其冲的是 编程思想

2.4.1 声明性编程范式

在Compose之前,咱们最常见的界面更新办法是运用 findViewById() 办法找到UI控件,并经过调用 button.setText(String)container.addChild(View)img.setImageBitmap(Bitmap) 等办法更新控件。

这种手动操做布局的办法,可以进步代码可读性,但也简单犯错。比方:A控件被移除后紧接着在另一段代码中又给A布局赋值。这或许会导致代码异常。

在曩昔的几年中,整个行业已开端转向声明性界面模型,该模型大大简化了与构建和更新界面相关的工程设计。该技能的作业原理是在概念上从头开端从头生成整个屏幕,然后仅履行必要的更改。此办法可避免手动更新有状况视图层次结构的复杂性。

简单点说,便是声明性布局可以做到只更新需求更新的布局,而不会由于一个小改动改写整个屏幕,这是功能进步的一大进步。

Compose 是一个声明性界面结构。

从头生成整个屏幕所面对的一个难题是,在时刻、核算才能和电池用量方面或许本钱昂扬。为了减轻这一本钱,**Compose 会智能地挑选在任何给定时刻需求从头制作界面的哪些部分。**这会对你设计界面组件的办法有一定影响,下面会提到。

2.4.2 简单的可组合函数

运用 Compose,你可以经过界说一组承受数据而宣布界面元素的可组合函数来构建界面。

下面一个简单的示例是 Greeting 组件,它承受 String 类型文案,而宣布一个显现问好消息的 Text 组件。

47696-71jthhgl94i.png

Greeting组件解析:

1.此函数带有 @Composable 注释。一切可组合函数都必须带有此注释;此注释可奉告 Compose 编译器:此函数旨在将数据转换为界面。

2.微件承受一个 String,因而它可以按称号问好用户。

3.此函数可以在界面中显现文本。为此,它会调用 Text() 可组合函数,该函数实际上会创立文本界面元素。可组合函数经过调用其他可组合函数来宣布界面层次结构。

4.此函数不会返回任何内容。宣布界面的 Compose 函数不需求返回任何内容,由于它们描绘所需的屏幕状况,而不是结构界面微件。

5.此函数快速、幂等且没有副效果。

  • 5.1 运用同一参数屡次调用此函数时,它的行为办法相同,而且它不运用其他值,如全局变量或对 random() 的调用。
  • 5.2 此函数描绘界面而没有任何副效果,如修正属性或全局变量。

2.4.3 声明性范式转变

在以往的 XML 布局编程时,通常会经过添加 XML 布局文件来完结布局扩张,每个 View 内部会保护自己状况,而且供给 gettersetter 办法,答应逻辑与 View 进行交互。

在 Compose 的声明性办法中, View 相对无状况,而且不供给 setter 或 getter 函数。

实际上, View 不会以目标方式供给。

你可以经过调用带有不同参数的同一可组合函数来更新界面。这使得向架构方式(如 ViewModel)供给状况变得很简单,如运用架构指南中所述。

然后,可组合项函数 负责在每次可调查数据更新时将当前运用状况转换为界面。

(下图示例:一个数据源像下传递,运用到每个布局,需求改写界面时,只需求改写数据源)
66403-uw4wckawpp.png

(下图示例:一个字布局动身点击事情时,事情向上传递,最后更改数据源,界面得以改写)
88852-bnf1vohdnms.png

2.4.4 动态内容

由于可组合函数是用 Kotlin 而不是 XML 编写的,因而它们可以像其他任何 Kotlin 代码相同动态。例如,假定你想要构建一个界面,用来问好一些用户

@Composable
fun Greeting(names: List<String>) {
for (name in names) {
Text("Hello $name")
}
}

此函数承受称号的列表,并为每个用户生成一句问好语。

可组合函数或许十分复杂。你可以依据功能,运用kotlin进行任意逻辑改造,一切这些动态切定制的内容,是 Compose对比传统xml的优势。

2.4.5 重组

在命令式界面模型中(XML界面模型),如需更改某个View,你可以在该View上调用 setter 以更改内部状况。

Compose 中,你可以运用新数据再次调用可组合函数。

这样做会导致函数进行重组 — 体系会依据需求运用新数据从头制作函数宣布的View。

Compose 结构可以智能地仅重组已更改的组件。

例如,假定有以下可组合函数,它用于显现一个按钮:

@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
Button(onClick = onClick) {
Text("I've been clicked $clicks times")
}
}

以上代码,每次点击该按钮时,调用方都会更新 clicks 的值。Compose 会再次调用 lambdaText 函数以显现新值;此进程称为“重组”。不依赖于该值的其他函数不会进行重组。

重组整个界面树在核算上本钱昂扬,由于会消耗核算才能并缩短电池续航时刻。

Compose 依据新输入重组时,它仅调用或许已更改的函数或 lambda,而越过其他函数或 lambda。经过越过一切未更改参数的函数或 lambdaCompose 可以高效地重组

切勿依赖于履行可组合函数所产生的顺便效应,由于或许会越过函数的重组。

顺便效应:是指对运用的其他部分可见的任何更改。

例如,以下操作全部都是危险的顺便效应:

  • 写入同享目标的属性
  • 更新 ViewModel 中的可调查项
  • 更新同享偏好设置

举个例子:

以下代码会创立一个可组合项以更新 SharedPreferences 中的值。

@Composable
fun SharedPrefsToggle(
text: String,
value: Boolean,
onValueChanged: (Boolean) -> Unit
) {
Row {
Text(text)
Checkbox(checked = value, onCheckedChange = onValueChanged)
}
}

该可组合项不应从同享偏好设置本身读取或写入,所以此代码将读取和写入操作移至后台协程中的 ViewModel。运用逻辑会运用回调传递当前值以触发更新。

2.4.6 运用Compose的留意事项

以下是在 Compose 中编程时需求留意的事项:

  • 可组合函数可以按任何次序履行。
  • 可组合函数可以并行履行。
  • 重组会越过尽或许多的可组合函数和 lambda。
  • 重组是乐观的操作,或许会被撤销。
  • 可组合函数或许会像动画的每一帧相同十分频频地运转。

在每种情况下,最佳做法都是使可组合函数坚持快速、幂等且没有顺便效应。

可组合函数可以按任何次序履行。

例如,假定有如下代码,用于在标签页布局中制作三个屏幕:

@Composable
fun ButtonRow() {
MyFancyNavigation {
StartScreen()
MiddleScreen()
EndScreen()
}
}

StartScreenMiddleScreenEndScreen 的调用可以按任何次序进行。这意味着,举例来说,你不能让 StartScreen() 设置某个全局变量(顺便效应)并让 MiddleScreen() 运用这项更改。相反,其中每个函数都需求坚持独立。

可组合函数可以并行履行。

Compose 可以经过并行运转可组合函数来优化重组。 这样一来,Compose 就可以运用多个中心,并以较低的优先级运转可组合函数(不在屏幕上)。

这种优化意味着,可组合函数或许会在后台线程池中履行。

假定多个可组合函数调用了 ViewModel 里的办法A,那么办法A会被多个线程调用,需求做好线程同步作业。

也由于可并行履行的特点,调用或许产生在与调用方不同的线程上。因而,一切可组合函数都不应有顺便效应(比方修正一个全局变量),而应经过一直在界面线程上履行的 onClick 等回调触发顺便效应。

以下示例展现了一个可组合项,它显现一个列表及其项数:

@Composable
fun ListComposable(myList: List<String>) {
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
}
}
Text("Count: ${myList.size}")
}
}

此代码没有顺便效应,它会将输入列表转换为界面。此代码十分适合显现小列表。不过,假如函数写入局部变量,则这并非线程安全或正确的代码:

@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
var items = 0
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
items++ // Avoid! Side-effect of the column recomposing.
}
}
Text("Count: $items")
}
}

在本例中,每次重组时,都会修正 items。这可以是动画的每一帧,或是在列表更新时。但不管怎样,界面都会显现错误的项数。因而,Compose 不支撑这样的写入操作;经过禁止此类写入操作,咱们答应结构更改线程以履行可组合 lambda。

重组会越过尽或许多的可组合函数和 lambda。

假如界面的某些部分无效,Compose 会极力只重组需求更新的部分。这意味着,它可以越过某些内容以从头运转单个按钮的可组合项,而不履行界面树中在其上面或下面的任何可组合项。

每个可组合函数和 lambda 都可以自行重组。以下示例演示了在呈现列表时重组怎么越过某些元素:

/**
* Display a list of names the user can click with a header
*/
@Composable
fun NamePicker(
header: String,
names: List<String>,
onNameClicked: (String) -> Unit
) {
Column {
// this will recompose when [header] changes, but not when [names] changes
Text(header, style = MaterialTheme.typography.h5)
Divider()
// LazyColumn is the Compose version of a RecyclerView.
// The lambda passed to items() is similar to a RecyclerView.ViewHolder.
LazyColumn {
items(names) { name ->
// When an item's [name] updates, the adapter for that item
// will recompose. This will not recompose when [header] changes
NamePickerItem(name, onNameClicked)
}
}
}
}
/**
* Display a single name the user can click.
*/
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}

这些效果域中的每一个都或许是在重组期间履行的唯一一个效果域。当 header 产生更改时,Compose 或许会跳至 Column lambda,而不履行它的任何父项。此外,履行 Column 时,假如 names 未更改,Compose 或许会挑选越过 LazyColumnItems

履行一切可组合函数或 lambda 都应该没有顺便效应。当你需求履行顺便效应时,应经过回调触发。

重组是乐观的操作,或许会被撤销。

重组是乐观的操作,也便是说,Compose 预计会在参数再次更改之前完结重组。假如某个参数在重组完结之前产生更改,Compose 或许会撤销重组,并运用新参数从头开端。

撤销重组后,Compose 会从重组中舍弃界面树。

如有任何顺便效应依赖于显现的界面,则即便撤销了组成操作,也会运用该顺便效应。这或许会导致运用状况不一致(导致状况紊乱,或重复赋值)。

可组合函数或许会像动画的每一帧相同十分频频地运转。

在某些情况下,或许会针对界面动画的每一帧运转一个可组合函数。假如该函数履行本钱昂扬的操作(例如从设备存储空间读取数据),或许会导致界面卡顿。

假如可组合函数需求数据,它应为相应的数据界说参数,从参数中获取。

你可以将本钱昂扬的作业移至组成操作线程之外的其他线程,并运用 mutableStateOfLiveData 将相应的数据传递给 Compose

**总结:**可组合函数应尽量写成纯函数,数据仅从参数中获取,更改数据仅从用户操作事情中进行。一切异步数据需求先准备好,再传入函数的参数中。

3 Compose是否值得一试

前面讲到Compose的特性,优缺点,以及怎么快速入门、怎么正确运用。

那么Compose是否值得运用到项目中来呢?

这些还需求具体情况具体分析。

假如你是新项目

我建议你大胆尝鲜,毕竟聪明的“部分改写”机制,是进步页面功能的重要手法。而且声明式布局在未来应该会替代传统的xml布局方式,这是大势所趋。

假如你是现有项目改造。

首要,你可以评估一下是否已经具备开端Compose的根底才能——kotlin言语的灵活运用

Compose可以说是为Kotlin量身定制的、与View model紧密结合的一种衍生物,有了KotlinView modelCompose的效果可以发挥到极致,也就能完结前面的目标:

  • 构建时刻可以 削减 29%
  • XML 行数大幅削减了 76%
  • APK 巨细减缩了 41%
  • 办法数削减了 17%

假如你已经具备了上述才能,那么可以在小范围进行试点,或许从功能要求比较高的页面入手。

建议先单个页面引进,最后再做全量替换。Google官方的改造案例也是这么做的。

最后,放开手,撸起来吧!

社区需求你我共建,更需求走在前沿的实践者,等待看到更多、更好的文章出现,这便是我写作的动力。

发表评论

提供最优质的资源集合

立即查看 了解详情