在今年的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界面,状态变化时,界面按照先前描述的重新“渲染”即可得到状态绝对正确的界面,而不用像命令一样,告诉程序一步一步该干什么,维护各种状态。扯远了,这个并不是今天文章的重点,稍微了解一下就好,其他的就不在本文延伸。关于声明式的更多介绍,建议看看这篇文章:https://zhuanlan.zhihu.com/p/68275232
我们回到本文的重点Jetpack Compose。
二、Jetpack Compose 介绍
Jetpack Compose 是一个用于构建原生Android UI 的现代化工具包,它基于声明式的编程模型,因此你可以简单地描述UI的外观,而Compose则负责其余的工作-当状态发生改变时,你的UI将自动更新。由于Compose基于Kotlin构建,因此可以与Java编程语言完全互操作,并且可以直接访问所有Android和Jetpack API。它与现有的UI工具包也是完全兼容的,因此你可以混合原来的View和现在新的View,并且从一开始就使用Material和动画进行设计。
三、Jetpack Compose 环境准备和Hello World
每当我们学习一门新的语言,我们都是从一个hello world
开始,今天我们也从一个hello world
来开始Jetpack Compose 吧! 要想获得Jetpack Compose 的最佳体验,我们需要下载最新版本的Android Studio 预览版本(即Android Studio 4.0)。因为Android Studio 4.0 添加了对Jetpack Compose 的支持,如新的Compose 模版和Compose 及时预览。
Android Studio 4.0.png
使用Jetpack Compose 来开始你的开发工作有2种方式:
- 将Jetpack Compose 添加到现有项目
- 创建一个支持Jetpack Compose的新应用
接下来分别介绍一下这两种方式。
1. 将Jetpack Compose 添加到现有项目
如果你想在现有的项目中使用Jetpack Compose,你需要配置一些必须的设置和依赖:
(1)gradle 配置
在app目录下的build.gradle
中将app支持的最低API 版本设置为21或更高,同时开启Jetpack Compose enable
开关,代码如下:
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"
}
}
(2) 使用试验版Kotlin-Gradle
插件
Jetpack Compose 需要试验版的Kotlin-Gradle
插件,在根目录下的build.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' }
}
}
(3) 添加Jetpack Compose工具包依赖项
在app目录下的build.gradle
添加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'
...
}
ok,到这儿准备工作就完毕,就可以开始写代码了,但是前面说了,还有一种方式接入Jetpack Compose ,我们来一起看看。
2. 创建一个支持Jetpack Compose的新应用
比起在现有应用中接入Jetpack Compose ,创建一个支持Jetpack Compose 的新项目则简单了许多,因为Android Studio 4.0 提供了一个新的Compose 模版,只要选择这个模版创建应用,则所有上面的那些配置项都自动帮我们完成了。
创建一个支持Jetpack Compose 的应用,如下几个步骤就可以了:
- 1.如果你在Android Studio的欢迎窗口,点击
Start a new Android Studio project
,如果你已经打开了Android Studio 项目,则在顶部菜单栏选择File > New > New Project
- 2. 在
Select a Project Template
窗口,选择Empty Compose Activity
并且点击下一步 - 3. 在
Configure your project
窗口,做如下几步: a. 设置项目名称
,包名
和保存位置
b. 注意,在语言下来菜单中,Kotlin 是唯一一个可选项,因为Jetpack Compose 只能用Kotlin来写的才能运行。
c. Minimum API level
下拉菜单中,选择21或者更高
- 4点击
Finish
现在,你就可以使用Jetpack Compose 来编写你的应用了。
3. Hello wold
在MainActivity.kt
中添加如下代码:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text("Hello, Android技术杂货铺")
}
}
}
hello.png
Jetpack Compose是围绕composable
函数来构建的。这些函数使你可以通过描述应用程序的形状和数据依赖,以编程方式定义应用程序的UI,而不是着眼于UI的构建过程。要创建composable
函数,只需要在函数名前面加上一个@composable
注解即可, 上面的Text
就是一个composable
函数。
4. 定义一个composable
函数
一个composable
函数只能在另一个composable
函数的作用域里被调用,要使一个函数变为composable
函数,只需在函数名前加上@composable
注解,我们把上面的代码中,setContent
中的部分移到外面,抽取到一个composable函数中,然后传递一个参数name
给 text
元素。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Greeting("Android技术杂货铺")
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
四、布局
UI元素是分层级的,元素包含在其他元素中。在Jetpack Compose中,你可以通过从其他composable
函数中调composable
函数来构建UI层次结构。
在Android的xml
布局中,如果要显示一个垂直结构的布局,最常用的就是LinearLayout
, 设置android:orientation
值为vertical
, 子元素就会垂直排列,那么,在Jetpack Compose 中,如何来实现垂直布局呢?先添加几个Text
来看一下。
1. 添加多个Text
在上面的例子中,我们添加了一个Text
显示文本,现在我们添加三个文本,代码如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NewsStory()
}
}
}
@Composable
fun NewsStory() {
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
image.png
从上图可以看到,我们添加了3个文本,但是,由于我们还没有提供有关如何排列它们的任何信息,因此三个文本元素相互重叠绘制,使得文本不可读。
2. 使用Column
要使重叠绘制的Text
文本能够垂直排列,我们就需要使用到Column
函数,写过flutter的同学看起来是不是很眼熟?是的,跟flutter里面的Column Widget 名字和功能完全一样,甚至连他们的属性都一摸一样。
@Composable
fun NewsStory() {
Column { // 添加Column,使布局垂直排列
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
}
效果如下:
可以看到,前面重叠的布局,现在已经垂直排列了,但是,默认情况下,从左上角开始,一个接一个的排列,没有任何间距。接下来,我们给Column
设置一些样式。
3. 给Column
添加样式
在调用Column()
时,可以传递参数给Column()
来配置Column
的大小、位置以及设置子元素的排列方式。
@Composable
fun NewsStory() {
Column (
crossAxisSize = LayoutSize.Expand,
modifier = Spacing(16.dp)
){ // 添加Column,使布局垂直排列
Text("我超❤️JetPack Compose的!")
Text("Android技术杂货铺")
Text("依然范特西")
}
}
image.png
如上图所示,我们填充了padding,其他效果几乎一摸一样, 上面代码中的设置属性解释如下:
crossAxisSize
: 指定Column
组件(注:Compose中,所有的组件都是composable函数,文中的组件都是指代composable函数
)在水平方向的大小,设置crossAxisSize
为LayoutSize.Expand
即表示Column宽度应为其父组件允许的最大宽度,相当于传统布局中的match_parant
,还有一个值为LayoutSize.Wrap
,看名字就知道,包裹内容,相当于传统布局中的wrap_content
。modifier:
使你可以进行其他格式更改。在这种情况下,我们将应用一个Spacing
修改器,该设置将Cloumn
与周围的视图产生间距。
4. 如何显示一张图片?
在原来的安卓原生布局中,显示图片有相应的控件ImageView
,设置本地图片地址或者Bitmap
就能展示,在Jetpack Compose 中该如何显示图片呢?
header.png
我们先下载这张图片到本地,添加到资源管理器中,命名为header.png
, 我们更改一下上面的NewsStory ()
方法,先从资源文件夹获取图片image,然后通过DrawImage()
将图片绘制出来:
@Composable
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("依然范特西")
}
}
image.png
可以看到,图片不会按正确的比列显示,接下来,我们来修复它。
图片已添加到布局中,但会展开以填充整个视图,并和文本是拼叠排列,文字显示在上层。要设置图形样式,请将其放入Container
(又一个和flutter中一样的控件)
Container
: 一个通用的内容对象,用于保存和安排其他UI元素。然后,你可以将大小和位置的设置应用于容器。
@Composable
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("依然范特西")
}
}
image.png
expanded :
指定Container的大小,默认是false
(Container的大小是子组件的大小,相当于wrap_content
),如果将它设置为true
,就指定Container的大小为父控件所允许的最大size, 相当于match_parent
。height :
设置Container容器的高度,height
属性的优先级高于expanded
,因此会覆盖expanded
,如上面的例子,设置height
为180dp
,也就是容器宽为父控件宽度,高为180dp
5. 添加间距Spacer
我们看到,图片和文本之间没有间距,传统布局中,我们可以添加Margin
属性,设置间距,在Jetpack Compose 中,我们可以使用HeightSpacer()
和WidthSpacer()
来设置垂直和水平间距
HeightSpacer(height = 20.dp) //设置垂直间距20dp
WidthSpacer(width = 20.dp) // 设置水平间距20dp
在上面的例子中,我们来为图片和文本之间添加20dp
的间距:
@Composable
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("依然范特西")
}
}
image.png
五、使用Material design 设计
Compose 旨在支持Material Design 设计原则,许多组件都实现了Material Design 设计,可以开箱即用,在这一节中,将使用一些Material小组件来对app进行样式设置
1. 添加Shape
样式
Shape
是Material Design 系统中的支柱之一,我们来用clip
函数对图片进行圆角裁剪。
@Composable
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("依然范特西")
}
}
形状是不可见的,但是我们的图片已经被裁剪了成了设置的形状样式,因此如上图,图片已经有圆角了。
2. 给Text
添加一些样式
通过Compose,可以轻松利用Material Design原则。将MaterialTheme()
应用于创建的组件
@Composable
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的默认文本样式。接下来,我们将特定的段落样式应用于每个文本元素。
@Composable
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
}
}
}
现在看看,我们的文本样式已经有变化了,标题有6中样式 h1-h6
,其实HTML
中的样式很像,内容文本有body1
和body2
2中样式。
Material 调色版使用了一些基本颜色,如果要强调文本,可以调整文本的不透明度
:
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 ,因此,我们可以设置文本的最大显示行数
,超过部分就截断。
如本例所示,我们设置显示最大行数为2
,多于的部分截断处理:
Text("我超❤️JetPack 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
就可以刷新预览
这真的是一个非常棒的功能,像其他声明式布局,如React 、flutter 是没有这个功能的,布局了之后,要重新运行才能看到效果,虽然可以热启动,但是还是没有这个预览来得直接。
还有一个非常牛逼的地方是,compose 的预览可以同时预览多个composable
函数。
效果如下:
七、总结
Jetpack Compse 目前还是试验版
,所以还存在很多问题,还不能现在将其用于商业项目中,但是这并不能妨碍我们学习和体验它,声明式 UI 框架近年来飞速发展,React 为声明式 UI 奠定了坚实基础并。Flutter 的发布将声明式 UI 的思想成功带到移动端开发领域,Apple和Google 分别先后发布了自己的声明式UI框架SwiftUI 和 Jetpack Compose , 以后,原生UI布局,声明式可能将会是主流。