在 2019 年的 Google/IO 大会上,亮相了一个全新的 Android 原生 UI 开发框架 - Jetpack Compose,与苹果的 SwiftIUI 一样,Jetpack Compose 是一个声明式的 UI 框架,随着安卓和苹果两大移动平台相继推出自己的 UI 开发框架 Jetpack Compose 和 SwiftIUI,标志着移动操作系统正式全面拥抱声明式 UI 开发模式。
声明式 UI 的前世今生
其实声明式 UI 并不是什么新技术,早在 2006 年,微软就已经发布了其新一代界面开发框架 WPF,其采用了 XAML 标记语言,支持双向数据绑定、可复用模板等特性。
2010 年,由诺基亚领导的 Qt 团队也正式发布了其下一代界面解决方案 Qt Quick,同样也是声明式,甚至 Qt Quick 起初的名字就是 Qt Declarative。QML 语言同样支持数据绑定、模块化等特性,此外还支持内置 JavaScript,开发者只用 QML 就可以开发出简单的带交互的原型应用。
声明式 UI 框架近年来飞速发展,并且被 Web 开发带向高潮。React 更是为声明式 UI 奠定了坚实基础并一直引领其未来的发展。随后 Flutter 的发布也将声明式 UI 的思想成功带到移动端开发领域...
声明式 UI 的意思就是,描述你想要一个什么样的 UI 界面,状态变化时,界面按照先前描述的重新 "渲染" 即可得到状态绝对正确的界面,而不用像命令一样,告诉程序一步一步该干什么,维护各种状态。
Jetpack Compose 介绍
Jetpack Compose 环境准备和 Hello World
Android Studio 4.0.png
使用 Jetpack Compose 来开始你的开发工作有两种方式:
将 Jetpack Compose 添加到现有项目
创建一个支持 Jetpack Compose 的新应用
gradle 配置
android {
defaultConfig {
...
minSdkVersion 21
}
buildFeatures {
Enables Jetpack Compose for this module
compose true
}
...
Set both the Java and Kotlin compilers to target Java 8.
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
使用试验版 Kotlin-Gradle 插件
buildscript {
repositories {
google()
jcenter()
// To download the required version of the Kotlin-Gradle plugin,
// add the following repository.
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
...
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0-alpha01'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.60-eap-25'
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
}
}
添加 Jetpack Compose 工具包依赖项
dependencies {
// You also need to include the following Compose toolkit dependencies.
implementation 'androidx.ui:ui-tooling:0.1.0-dev02'
implementation 'androidx.ui:ui-layout:0.1.0-dev02'
implementation 'androidx.ui:ui-material:0.1.0-dev02'
...
}
创建一个支持 Jetpack Compose 的新应用
创建一个支持 Jetpack Compose 的应用,如下几个步骤就可以了:
如果你在 Android Studio 的欢迎窗口,点击 Start a new Android Studio project,如果你已经打开了 Android Studio 项目,则在顶部菜单栏选择 File > New > New Project
在 Select a Project Template 窗口,选择 Empty Compose Activity 并且点击下一步
在 Configure your project 窗口,做如下几步:
设置项目名称,包名和保存位置
注意,在语言下来菜单中,Kotlin 是唯一一个可选项,因为 Jetpack Compose 只能用 Kotlin 来写的才能运行。
Minimum API level 下拉菜单中,选择 21 或者更高
点击 Finish
现在,你就可以使用 Jetpack Compose 来编写你的应用了。
Hello wold
class MainActivity : AppCompatActivity() {
overridefun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text("Hello, Android技术杂货铺")
}
}
}
Jetpack Compose 是围绕 composable 函数来构建的。这些函数使你可以通过描述应用程序的形状和数据依赖,以编程方式定义应用程序的 UI,而不是着眼于 UI 的构建过程。要创建 composable 函数,只需要在函数名前面加上一个 @composable 注解即可,上面的 Text 就是一个 composable 函数。
一个 composable 函数只能在另一个 composable 函数的作用域里被调用,要使一个函数变为 composable 函数,只需在函数名前加上 @composable 注解,我们把上面的代码中,setContent 中的部分移到外面,抽取到一个 composable 函数中,然后传递一个参数 name 给 text 元素。
class MainActivity : AppCompatActivity() {
overridefun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Greeting("Android技术杂货铺")
}
}
}
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
布局
UI 元素是分层级的,元素包含在其他元素中。在 Jetpack Compose 中,你可以通过从其他 composable 函数中调 composable 函数来构建 UI 层次结构。
class MainActivity : AppCompatActivity() {
overridefun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NewsStory()
}
}
}
fun NewsStory() {
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
fun NewsStory() {
Column { // 添加Column,使布局垂直排列
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
}
给 Column 添加样式
fun NewsStory() {
Column (
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
){ // 添加Column,使布局垂直排列
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
}
如上图所示,我们填充了 padding,其他效果几乎一模一样,上面代码中的设置属性解释如下:
crossAxisSize: 指定 Column 组件 (注: Compose 中,所有的组件都是 composable 函数,文中的组件都是指代 composable 函数) 在水平方向的大小,设置 crossAxisSize 为 LayoutSize.Expand 即表示 Column 宽度应为其父组件允许的最大宽度,相当于传统布局中的 match_parant,还有一个值为 LayoutSize.Wrap,看名字就知道,包裹内容,相当于传统布局中的 wrap_content。
modifier: 使你可以进行其他格式更改。在这种情况下,我们将应用一个 Spacing 修改器,该设置将 Cloumn 与周围的视图产生间距。
fun NewsStory() {
// 获取图片
val image = +imageResource(R.mipmap.header)
Column (
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
){ // 添加Column,使布局垂直排列
// 显示图片
DrawImage(image)
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
}
图片已添加到布局中,但会展开以填充整个视图,并和文本是拼叠排列,文字显示在上层。要设置图形样式,请将其放入 Container (又一个和 flutter 中一样的控件)
Container: 一个通用的内容对象,用于保存和安排其他 UI 元素。然后,你可以将大小和位置的设置应用于容器。
fun NewsStory() {
// 获取图片
val image = +imageResource(R.mipmap.header)
Column (
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
){ // 添加Column,使布局垂直排列
// 放在容器中,设置大小
Container(expanded = true, height = 180.dp) {
// 显示图片
DrawImage(image)
}
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
}
添加间距 Spacer
我们看到,图片和文本之间没有间距,传统布局中,我们可以添加 Margin 属性,设置间距,在 Jetpack Compose 中,我们可以使用 HeightSpacer() 和 WidthSpacer() 来设置垂直和水平间距。
HeightSpacer(height = 20.dp) //设置垂直间距20dp
WidthSpacer(width = 20.dp) // 设置水平间距20dp
在上面的例子中,我们来为图片和文本之间添加 20dp 的间距:
fun NewsStory() {
// 获取图片
val image = +imageResource(R.mipmap.header)
Column (
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
){ // 添加Column,使布局垂直排列
// 放在容器中,设置大小
Container(expanded = true, height = 180.dp) {
// 显示图片
DrawImage(image)
}
HeightSpacer(height = 20.dp) // 添加垂直方向间距20dp
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
}
使用 Material design 设计
添加 Shape 样式
Shape 是 Material Design 系统中的支柱之一,我们来用 clip 函数对图片进行圆角裁剪。
fun NewsStory() {
// 获取图片
val image = +imageResource(R.mipmap.header)
Column (
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
){ // 添加Column,使布局垂直排列
// 放在容器中,设置大小
Container(expanded = true, height = 180.dp) {
Clip(shape = RoundedCornerShape(10.dp)) {
// 显示图片
DrawImage(image)
}
}
HeightSpacer(height = 20.dp) // 添加垂直方向间距20dp
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
}
形状是不可见的,但是我们的图片已经被裁剪了成了设置的形状样式,因此如上图,图片已经有圆角了。
fun NewsStory() {
// 获取图片
val image = +imageResource(R.mipmap.header)
// 使用Material Design 设计
MaterialTheme() {
Column (
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
){ // 添加Column,使布局垂直排列
// 放在容器中,设置大小
Container(expanded = true, height = 180.dp) {
Clip(shape = RoundedCornerShape(10.dp)) {
// 显示图片
DrawImage(image)
}
}
HeightSpacer(height = 20.dp) // 添加垂直方向间距20dp
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
}
}
如上面的代码,添加了 MaterialTheme 后,重新运行,效果没有任何变化,文本现在使用了 MaterialTheme 的默认文本样式。接下来,我们将特定的段落样式应用于每个文本元素。
fun NewsStory() {
// 获取图片
val image = +imageResource(R.mipmap.header)
// 使用Material Design 设计
MaterialTheme() {
Column (
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
){ // 添加Column,使布局垂直排列
// 放在容器中,设置大小
Container(expanded = true, height = 180.dp) {
Clip(shape = RoundedCornerShape(10.dp)) {
// 显示图片
DrawImage(image)
}
}
HeightSpacer(height = 20.dp) // 添加垂直方向间距20dp
Text("我超❤️JetPack Compose的!", style = +themeTextStyle { h5 }) // 注意添加了style
Text("Android技术杂货铺", style = +themeTextStyle { body1 }) // 注意添加了style
Text("依然范特西", style = +themeTextStyle { body2 }) // 注意添加了style
}
}
}
Text("我超❤️JetPack Compose的!", style = (+themeTextStyle { h5 }).withOpacity(0.87f))
Text("Android技术杂货铺", style = (+themeTextStyle { body1 }).withOpacity(0.87f))
Text("依然范特西", style = (+themeTextStyle { body2 }).withOpacity(0.6f))
有些时候,标题很长,但是我们又不想长标题换行从而影响我们的 app UI,因此,我们可以设置文本的最大显示行数,超过部分就截断。
Compose的!写起来简单,复用性又强,可以抽取很多组件来复用,不用管理复杂的状态变更!",
maxLines = 2, overflow = TextOverflow.Ellipsis,
style = (+themeTextStyle { h5 }).withOpacity(0.87f))
可以看到,设置了 maxLines 和 overflow 之后,超出部分就截断处理了,不会影响到整个布局样式。
Compose 布局实时预览
从 Android Studio 4.0 开始,提供了在 IDE 中预览 composable 函数的功能,不用像以前那样,要先下载一个模拟器,然后将 app 状态模拟器上,运行 app 才能看到效果。但是有一个限制,那就是 composable 函数不能有参数,满足下面两个条件:
函数没有参数
在函数前面添加 @Preview 注解
当布局改变了之后,顶部会出现一个导航条,显示预览已经过期,点击 build&Refresh 就可以刷新预览。
总结
长按右侧二维码
查看更多开发者精彩分享
"开发者说·DTalk" 面向中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐。
点击屏末 | 阅读原文 | 即刻报名参与 "开发者说·DTalk"