用Jetpack Compose构建更快的安卓应用

Jetpack Compose是谷歌用于Android应用开发的新的、现代的、声明式的UI框架。Compose将十多年来从以前的UI工具包中学到的知识与Kotlin编程语言的力量结合起来,为Android开发者提供了一个强大的、令人耳目一新的UI开发体验。

本文将通过以下方式启动你对Jetpack Compose的学习。

  1. 快速展示Compose UI应用程序的构建模块
  2. 介绍一些编写更灵活和可扩展的Composable代码的准则
  3. 详述在使用Compose时提高开发者生产力的几个技巧

什么是Jetpack Compose?

Jetpack Compose与传统的Android视图系统最明显的区别是,Compose UI完全由Kotlin编写。不再需要使用XML来进行布局、风格、排版、颜色或其他UI相关的元素。

@Composable
fun Composable() {
    Text("Hello World!")
} 

你所熟悉的视图、TextViews 、按钮、LinearLayouts 等已经不复存在。Compose应用程序是使用可组合函数而不是视图函数构建的。可组合函数被注释为@Composable ,并代表单个UI元素。我们可以使用预定义的可组合函数或定义我们自己的。

Jetpack Compose利用了其他声明式框架的模式,如React和Flutter,为那些习惯于声明式编程的人提供了直观和熟悉的体验。因为Compose应用程序是用Kotlin编写的,所以很容易使用所有你习惯使用的控制流结构和语言功能来描述你的用户界面。

@Composable
fun Title(message: String?) {
  if(message == null) {
    Text("error")
  } else {
    Text(message)
  }
} 

Compose有望简化和加速UI开发,并有很大潜力成为使用Kotlin的Android开发的未来。那么,你如何开始构建Jetpack Compose应用程序?

使用Jetpack Compose

要开始使用Jetpack Compose,你需要做几件事来设置你的项目

  1. 下载最新的稳定版本的Android Studio
  2. 使用Android Studio创建一个新的Android项目
  3. 将你的应用程序的minSdk版本设置为21(Android 5.0)或更高版本
  4. 在你的build.gradle 文件中添加以下Compose依赖项
// build.gradle.kts
implementation("androidx.compose.ui:ui:1.0.1")
implementation("androidx.compose.ui:ui-tooling:1.0.1")
implementation("androidx.compose.foundation:foundation:1.0.1")
implementation("androidx.compose.material:material:1.0.1") 

一旦你创建了你的项目并添加了这些依赖项,你就应该能够同步你的项目并开始使用 Compose APIs。首先,让我们在创建的默认Activity 中显示一条 "Hello World "信息。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
} 

注意没有调用setContentView() 。我们可以使用setContent{} 函数为Activity 定义一个基于Compos的用户界面,而不是充气一个基于XML的布局。在传递给setContent() 的lambda中,我们可以添加Composables来定义我们的用户界面。

让我们看看一些最常见的构件,你需要用Jetpack Compose开始构建交互式应用程序。

掌握可组合的基础知识

添加文本

需要在你的应用程序中添加一些文本吗?你可能会想使用Text Composable。

setContent {
 Text(text = "Hello World")
} 

Text Composable是一个Kotlin函数,包括一些可选的参数,使你能够配置你的文本应该是什么样子。

setContent {
 Text(
   text = "Hello World",
   fontWeight = FontWeight.Bold,
   maxLines = 1,
   overflow = TextOverflow.Ellipsis
 )
} 

按钮可组合

要在你的用户界面中显示一个简单的按钮,你可以使用Button 可组合式。

Button(onClick = {}) {
 Text("Button Text")
} 

Button 是一个很好的例子,说明Compose偏向于组合而不是严格继承。请注意,我们向Button 函数传递了一个lambda。这个lambda定义了Button 的内容。在这种情况下,我们按钮的内容是一个简单的文本元素。

这是在Compose中的一个常见模式。可组合函数通常会用一个尾部的lambda来定义它们的内容,使它们更灵活,更容易操作。

ColumnRow 可组合物

为了帮助对齐元素,如TextButton ,我们需要容器来帮助我们安排这些元素。你可能会遇到的前两个这样的容器是 [Column](https://developer.android.com/jetpack/compose/layouts/basics#standard-layouts)[Row](https://developer.android.com/jetpack/compose/layouts/basics#standard-layouts).

Column 将垂直地铺设子内容,一个接一个。

Column(
 modifier = Modifier.fillMaxSize(1f),
 verticalArrangement = Arrangement.Center,
 horizontalAlignment = Alignment.CenterHorizontally
) {
 Text(
   text = "Hello World",
   fontWeight = FontWeight.Bold,
   maxLines = 1,
   overflow = TextOverflow.Ellipsis
 )

 Button(onClick = {}) {
   Text("Button Text")
 }
} 
Vertical Button Text

如果你需要水平的内容,你可以使用Row 而不是Column

Row(
 modifier = Modifier.fillMaxSize(1f),
 verticalAlignment = Alignment.CenterVertically,
 horizontalArrangement = Arrangement.Center
) {
 Text(
   text = "Hello World",
   fontWeight = FontWeight.Bold,
   maxLines = 1,
   overflow = TextOverflow.Ellipsis
 )

 Button(onClick = {}) {
   Text("Button Text")
 }
} 
Horizontal Button Text Example

LazyColumnLazyRow 用于大型数据集

在处理大型数据集时,需要一个高性能的容器?与其使用ColumnRow ,不如使用LazyColumn ,和LazyRow ,它们可以提供更有效的资源回收和更快的绘图性能。这两个组合体在概念上与RecyclerView 非常相似。

要创建一个LazyColumn ,我们可以把Column 改为LazyColumn ,然后在一个item{} 可组合式中定义每个UI元素,它定义了列表中的单个项目,因此我们可以定义不同的项目类型而不需要适配器。

LazyColumn(
 verticalArrangement = Arrangement.Center,
 horizontalAlignment = Alignment.CenterHorizontally,
 modifier = Modifier.fillMaxSize(1f),
) {
 item { Text("1") }
 item { Text("2") }
 item { Text("3") }
 item { Text("4") }
 item { Text("5") }
 item { Text("6") }
 item { Text("7") }
 item { Text("8") }
 item { Text("9") }
 item { Text("10") }
} 
Vertical Line of Numbers

需要根据一个静态计数,或一个数据集合来创建一堆项目?我们可以使用items() 函数在我们的LazyColumn 中重复创建项目。

LazyColumn(
 verticalArrangement = Arrangement.Center,
 horizontalAlignment = Alignment.CenterHorizontally,
 modifier = Modifier.fillMaxSize(1f),
) {
 items(100) { index ->
   Text("$index")
 }
} 
Vertical Line With More Numbers

LazyColumnLazyRow 内的项目不一定是相同的。你可以自由地混合你想要的UI元素--所有这些都不需要单独的适配器或布局。这突出了Jetpack Compose相对于现有UI系统的力量和灵活性。

在 Compose 中处理 padding 和 margin

我们如何在 Compose 中处理 padding 和 margin 呢?好吧,Compos通过提供一个概念来简化它--padding。

我们可以通过应用以下方法来定义任何元素的衬垫 [Modifier](https://developer.android.com/jetpack/compose/layouts/basics#modifiers).修改器允许我们配置一个可组合元素,以控制诸如尺寸、填充、焦点状态、点击处理程序等。

为了给前面的例子中的项目列表添加填充,我们可以更新我们的代码如下。

LazyColumn(
 verticalArrangement = Arrangement.Center,
 horizontalAlignment = Alignment.CenterHorizontally,
 modifier = Modifier.fillMaxSize(1f),
) {
 items(100) { index ->
   Text(
     text = "$index",
     modifier = Modifier.padding(16.dp)
   )
 }
} 
Vertical Line of Fewer Numbers

这里有几件有趣的事情要做。首先,命名参数的使用在Compose代码中是很常见的。第二,我们可以通过直接引用Modifier ,并对其调用方法来开始构建一个修改器。

为了增加填充,我们可以调用padding() ,并传入一个尺寸值。在这里,我们使用了一个叫做dp 的扩展属性来方便地将原始值16 转换为一个尺寸值。

更好的组合函数的提示

我们现在已经涵盖了最常见的组合体,以及一些常见的使用模式。随着安卓社区继续使用合成器,新的模式和惯例,最佳实践将会出现。

当你使用Jetpack Compose时,这里有一些提示可以帮助你写出更灵活、可扩展的Compose应用程序。

充分利用默认参数值和命名参数

与Java相比,将Kotlin用于Compose的一大优势是,我们可以利用Kotlin语言的特性,如命名参数和默认参数值。

命名参数允许我们在调用函数时混合参数的顺序。这使得我们的Composable函数更容易操作,并能帮助使代码非常可读。

默认参数允许我们编写一个可以以多种方式使用的可组合函数。与其定义许多重载函数,我们可以使用一个单一的函数来定义合理的默认值。

观察我们一直在使用的Text 函数。

@Composable
fun Text(
   text: String,
   modifier: Modifier = Modifier,
   color: Color = Color.Unspecified,
   fontSize: TextUnit = TextUnit.Unspecified,
   fontStyle: FontStyle? = null,
   fontWeight: FontWeight? = null,
   fontFamily: FontFamily? = null,
   letterSpacing: TextUnit = TextUnit.Unspecified,
   textDecoration: TextDecoration? = null,
   textAlign: TextAlign? = null,
   lineHeight: TextUnit = TextUnit.Unspecified,
   overflow: TextOverflow = TextOverflow.Clip,
   softWrap: Boolean = true,
   maxLines: Int = Int.MAX_VALUE,
   onTextLayout: (TextLayoutResult) -> Unit = {},
   style: TextStyle = LocalTextStyle.current
) { ... } 

这个函数提供了多种方法来控制文本在绘制时的外观。然而,由于唯一需要的参数是text ,这个Composable的基本用法可以避免全套参数的复杂性。

小型和私有函数

当你构建你的屏幕和可组合软件时,尽量保持你的单个可组合软件小而集中。将UI的各个部分重构为较小的函数以限制其范围,并使你的代码更容易阅读和遵循。

你也可以利用可见性修改器来帮助你保持代码的条理性。想象一下,你正在为一个特定的屏幕构建用户界面,并且你把所有的可组合程序放在一个文件中。

如果该文件只暴露了一个公共的或内部的可组合物,那么你就可以在该文件中使用任意多的小的、集中的、私有的可组合物,而不会污染你的项目的全局命名空间。

基于槽位的设计

当你建立你的自定义组合体时,从核心库和材料库的组合体提供者那里获得灵感。这些函数利用基于槽的方法来设计API。

许多Compos API不是硬性规定事物必须如何组成或使用,而是允许调用者定制内容应该如何绘制,因此可以根据特定的feeds进行定制。

例如,让我们重新审视我们前面的按钮例子。

Button(onClick = {}) {
 Text("Button Text")
} 

Button 并不接受一个字符串来显示文本。它让调用者决定文本应该如何显示。或者,我们根本不希望在我们的按钮中出现文本。通过这种基于槽的方法,我们可以为我们的按钮使用一个图标,或者一个复杂的图标、文本等的布局。

这种方法在创建低级别的组合体时可以得到回报,这些组合体可以在你的应用程序中重复使用。一个很好的例子是 "基础卡"。

@Composable
fun BaseCard(content: @Composable ColumnScope.() -> Unit) {
 Card(elevation = 4.dp) {
   Column(content = content)
 }
} 

你可以定义根卡的外观,但把内容留给调用者来传递。这种模式对于为你的团队建立一套可重复使用的组件非常有用。

生产力和开发者经验的提示

Jetpack Compose的主要目标之一是使Android开发更快、更愉快。考虑到这一点,有几件有用的事情需要注意。

可组合的预览

Jetpack Compose支持预览,这让我们可以预览我们的Composable是什么样子的,而不需要部署到设备上。小的变化,如更新一个填充常量,可以立即更新预览,而不需要任何形式的刷新或重建。

要定义一个预览,我们可以写一个新的可组合,并添加@Preview 注释。

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
 MyApplicationTheme {
   Greeting("Android")
 }
} 

然后,当我们构建我们的应用程序时,这些预览在Android Studio中是可见的。

Preview in Android Studio

预览也可以通过一些有趣的方式进行定制。

例如,可以对预览进行配置,如显示一个默认的背景,改变预览设备的大小,或改变黑暗主题。

@Preview(showBackground = true, device = Devices.PIXEL_4)
@Composable
fun Pixel4Preview() {
 MyApplicationTheme {
   Column(
     modifier = Modifier.fillMaxSize(1f),
     verticalArrangement = Arrangement.Center,
     horizontalAlignment = Alignment.CenterHorizontally
   ) {
     Greeting("Android")
   }
 }
} 

我们甚至可以在同一个文件中定义多个预览,以看到这些预览并排在一起,并看到我们的Composable在不同配置下的样子的多个例子。

Various Configurations Preview

通过利用这些预览,我们可以实现更快的迭代周期,因为我们可以在不部署我们的应用程序的情况下轻松地可视化我们的用户界面的变化。

用Jetpack Compose改善构建时间

可组合预览的一个潜在问题是,对于较大的代码变化,它们确实需要对你的应用程序进行一些重新构建。对于构建时间较慢的应用程序,这可能会使预览的作用大打折扣。

为了帮助解决这个问题,有两件事你可以做。

第一是通过在gradle.properties 文件中添加org.gradle.caching=true ,启用本地Gradle构建缓存。启用本地构建缓存后,每次刷新预览时,你的构建将不得不重建更少的代码,这应该会导致更快的预览。

除了启用本地Gradle构建缓存外,你还可以考虑将你的应用程序模块化。如果你能把你的Composable预览隔离到依赖性较小的模块,你可以减少刷新预览时必须重新编译的代码量。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容