Jetpack Compose快速上手
Jetpack Compose是安卓最新的UI工具,使用Kotlin以声明式的风格写界面。本文希望从Compose出发,通过较详细全面的讲解示范,介绍安卓开发的一些基本方法,解释一般文章或AI回复中不太会认真解释的问题,希望可以帮读者实现自己的想法,做出满意的安卓App。
环境配置
建议使用Android Studio开发,选择Compose模板即可开始。
API等级和构建工具可以保持默认(API 29,Kotlin DSL),有域名的话可以把域名反过来作为包名替代默认的com.example.myapplication。
Android Studio建议使用JetBrains Toolbox下载和管理。


界面编写
经过漫长的初始化和同步(视网络情况而定),等你看到右下角的进度条消失,项目就加载好了。 此时左上角的文件结构显示类型会变成Android,这个布局会用更少的层级展示项目文件。 展开app查看更细致的分类,其中manifest文件夹存放配置文件,写有权限、活动(Activity)等,如果解压过apk文件可能见过。 kotlin+java是主要代码,大多数时候都在这里工作,实现应用功能。 res存放各种资源文件,如图片、字符串等。用Java开发时还需要经常在这里定义菜单、界面等一堆麻烦事,但有了Compose就不需要啦。 下面的Gradle Scripts存放各种构建配置,例如依赖、Java版本、编译SDK。
我们直接关注安卓应用的核心代码,即项目默认展示的这个,MainActivity.kt。
// MainActivity.Kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MyDroidTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
MyDroidTheme {
Greeting("Android")
}
}
这就是整个页面背后的代码啦,如果你不熟悉Kotlin,可以在Learn X in Y minutes上扫一眼。总的来说,Kotlin是一门非常优雅简便的语言,甚至有些代码可能一时难以看懂,不过那些情况我们遇见时会讲的,先从这个主页学起。
就算你从没有接触过安卓开发,或许也可以看出,第一块代码是应用的主活动类,继承了库提供的某个类,并重写了它的onCreate方法。方法实现首先开启了EdgeToEdge,它可以让系统的状态栏和应用融合更好,你可以注释这一行看看有什么变化。
鼠标停留在函数上可以查看文档。也可以前往安卓开发网站的API reference搜索。
之后的setContent函数则是我们主要关心的部分。注意到在Compose里,UI布局是用函数表示的。
代码用Greeting函数描述了一个UI:空空的页面,其中有一行字。
Greeting外层用Scaffold包裹,这是官方推荐的应用布局(毕竟叫“脚手架”),Greeting属于它的“内容”这部分。
Scaffold外层又有MyDroidTheme包裹,这是整个应用的主题,定义在ui/theme/包的Theme.kt中。
Android Studio左侧文件结构与文件夹结构并不等同,这是由上方的
App项目结构显示下拉框决定的。 依次展开kotlin+java、包名、ui.theme即可看到Color.kt、Theme.kt、Type.kt。
Greeting函数的定义就在下方。它有@Compose注解,表示自己是一个Compose组件。
接收两个参数,要显示的文本,以及用于修改样式的修改器,函数内部还调用了Material 3的组件Text。
通过函数的层层调用,Compose用组合的方式搭建了表示页面的组件树,根组件为Scaffold,它的content参数为Greeting,而Greeting仅包含一个Text。
你可能会认为嵌套太深影响性能,实则不然,这正是Compose的运作方式。 Compose不使用继承现有组件做修改来创造新组件,而是建议把需要的功能自由组合,由此灵活的创造界面。
更下方的xxxPreview是一个预览组件,让你不必运行软件就能看到界面的样子,但软件功能早晚还是要运行查看的,本文中并未用到这一功能。
读者现在可以用数据线连接手机电脑,确保开发者模式中开启了USB调试,即可在Android Studio上方看见设备名称,点击运行或按Shift + F10即可运行项目。
在手机上,可以看见空白的页面左上角出现了Hello Android!,这就是现有的代码对应的界面啦。
组件介绍
实际上,由于组件用函数表示,你可以直接用Text代替Greeting达到一样的效果。
多使用一个函数是为了让代码更加清晰,把独立的一块拆成一个函数组件,以后也可以重复使用。
Scaffold是一个脚手架组件,把鼠标移到它上面,可以看到它的参数列表,其中大部分是有默认值的,不需要我们指定。
最后一个参数content参数为@Composable ((PaddingValues) -> Unit),即一个组件函数。
它就对应我们传入的Greeting吗?不,函数签名都不一致,怎么会呢。
实际上我们传入的是一个lambda表达式,参数命名为innerPadding,无返回值(即返回Unit),而这个传参的写法很有趣,它竟然在Scaffold参数括号的外面。
这是Kotlin语言的一个功能,当函数的最后一个参数为lambda表达式时,它可以被放到函数调用表达式的外面。不要与函数定义混淆噢。
我们在这个lambda中调用了Greeting函数,从而成功将这个组件塞进了Scaffold的内容部分。
来看看这几个参数,首先是脚手架内容lambda的参数innerPadding,它包含四个内边距,上对应状态栏的高度,下对应导航栏的高度(有侧滑返回的全面屏手机应该没有这个)。
通过把这几个边距作为参数供内容使用,我们就不需要手动拉开边距了。
这里的使用方法是把innerPadding使用padding方法施加给Modifier,创造出一个新的Modifier示例,传递给Text的对应参数。
最终Text应用这个修改器,文本就会与顶部有一个状态栏那么高的上边距了。
Scaffold还使用了一个Modifier.fillMaxSize()修改器,占满父元素剩余空间,不过它自己就是根元素,因此占满手机屏幕空间。
自由组合
接下来你想写什么界面呢?可以在安卓官方对Compose的介绍文档中翻看各种组件,用他们组合成想要的界面。 为了教学和熟悉Compose,我们还是示范一种很常用的页面,一个推文列表吧。
从官方文档的推荐看,列表应该使用Column或LazyColumn搭建。
前者适合普通的列式排列,适合仅仅想把几个组件竖着放的场合,而后者针对大量列表项做有优化,适合展示推文列表等。
想象一下推特、QQ空间的样子,你应该可以将它们拆分成几个组件。
每条推文的结构都是一致的,包含头像、呢称、操作、文本内容、点赞、时间以及数量不定的图片、评论。
这里我们简化成几类:仅供展示的固定项呢称、内容,可选的图片,可交互的点赞图标,此外还可以设置长按或点击的操作。
想好了这些,再搜索文档,你应该可以写出这样表示一条推文的极简PostCard组件(图像以后再学习吧):
@Composable
fun PostCard(name: String, content: String, liked: Boolean, comments: List<Pair<String, String>> = emptyList()) {
Text(name)
Text(content)
Icon(Icons.Default.ThumbUp, null)
comments.forEach {
Text(it.first)
Text(it.second)
}
}
这里最高级的特性也只是List和Pair类型、默认参数与lambda表达式。其中lambda的参数没有显式写名字,而是直接用it访问了。
想想它们的对齐方式与间距(就以QQ动态为参考),我们可以用更多组件与Modifier美化它:
@Composable
fun PostCard(
name: String,
content: String,
liked: Boolean,
comments: List<Pair<String, String>> = emptyList()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text(name, style = MaterialTheme.typography.bodyLarge)
Text(content, style = MaterialTheme.typography.bodyMedium)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
if (liked) {
Icon(Icons.Default.ThumbUp, null)
} else {
Icon(Icons.Outlined.ThumbUp, null)
}
}
comments.forEach {
Row(modifier = Modifier.fillMaxWidth()) {
Text(it.first + ":", style = MaterialTheme.typography.bodySmall)
Text(it.second, style = MaterialTheme.typography.bodySmall)
}
}
}
}
不妨先用它代替Greeting,用简单的Column作为列表,运行一次看看效果。
setContent {
ExampleTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) {
PostCard(
"名字",
"这是一条测试内容。今天天气真好。",
false,
listOf(Pair("系统", "你说的对"), Pair("系统", "你说的好"))
)
}
}
}
}

这里用到了几个新功能:
- 样式引用:我们从
MaterialTheme类中引用了样式,这其实是我们在Theme.kt中定义的样式,包含Material 3中定义的几种字体类型 - 条件判断:这些Kotlin代码非常直观的展示了根据参数
liked的不同显示不同组件的功能,非常方便吧~
你可能觉得内容间缺少边界,所有文字背景好像都融合在一起。可以通过添加Surface或Card、设置elevation参数和添加水平线解决。
如果你按住Ctrl键单击Card,你可以跳转到它在源码中的定义。原来它只是一个Surface加Column!