Jetpack Compose初体验

关于Jetpack Compose

Android Jetpack Compose是2019 Google/IO大会上推出的一种声明式的UI开发框架,经过一年左右的演进,现在到了alpha阶段。Jetpack Compose是用于构建原生界面的新款Android工具包。它可简化并加快Android上的界面开发。使用更少的代码、强大的工具和直观的KotlinAPI,快速让应用生动而精彩,从此不再需要写xml,使用声明式的Compose函数来构建页面UI。

听起来是不是很厉害的样子?以下是android官方介绍:

compose_feature
从介绍可以看来google对compose还是给予厚望的。

Jetpack Compose 目前为Alpha版。API Surface尚未最终确定,预计后续会有变动。

接下来就来开启我们的Compose之旅吧,刚好还可以顺带学习一下kotlin,compose版本基于1.0.0-alpha04

环境配置

安装Android Studio

首先需要安装最新Canary版Android Studio预览版本,当您搭配使用Android Studio和 Jetpack Compose开发应用时,可以从智能编辑器功能中受益,这些功能包括“新建项目”模板和立即预览 Compose 界面等。下载链接:https://developer.android.com/studio/preview

创建Jetpack Compose项目

关于如何创建Jetpack Compose项目,参考:https://developer.android.com/jetpack/compose/setup

基础入门

首先来认识一个非常重要的概念:@Composable
在Jetpack Compose中,一切UI的绘制均基于可组合函数。在开发Jetpack Compose程序过程中,基本上都是与@Composable这个注解打交道。使用Composable注解可以标记一个函数为可组合函数,可组合函数可用于描述UI界面中的具体展示内容或绘制规则。通过不同的Compose的组合或嵌套,可以很灵活的完成复杂UI的展示。
比如我们在界面上展示一个简单的text,代码如下:

1
2
3
4
5
6
7
8
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text(text = "hello world!")
}
}
}

运行效果如下:
compose_simple
是不是很神奇!
其实对于setContent来说,其接受@Compose注解的子元素,Text其实就是一个组合函数,在Jetpack Compose当中类似于TextView提供文本展示的能力。我们还可以换一种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
hello(name = "hello world!")
}
}
}

@Composable
fun hello(name: String) {
Text(text = name)
}

运行效果也是一样的。在Compose的世界里,万物皆@Compose注解。

上面只是简单的展示了一个文本,我们还可以给这个文本设置一些属性,比如常见的文本颜色,文字大小等,还可以使用Modifier来设置背景颜色、上下padding等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Greeting(name = "hello world!")
}
}
}

@Composable
fun Greeting(name: String) {
Text(
text = name,
fontSize = TextUnit.Companion.Sp(15),
fontStyle = FontStyle.Italic,
maxLines = 2,
color = Color.Blue,
modifier = Modifier.background(
color = Color.Red,
shape = RoundedCornerShape(20)
).padding(10.dp)
)
}

运行效果如下:
compose_text_advanced

预览

使用@Preview注解支持预览功能,只需要在@Composable函数上面添加@Preview注解是可以进行预览,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//预览
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
Greeting()
}

@Composable
fun Greeting(){
Greeting(name = "hello world!")
}

@Composable
fun Greeting(name: String) {
Text(
text = name,
fontSize = TextUnit.Companion.Sp(15),
fontStyle = FontStyle.Italic,
maxLines = 2,
color = Color.Blue,
modifier = Modifier.background(
color = Color.Red,
shape = RoundedCornerShape(20)
).padding(10.dp)
)
}

preview

接下来讲一下Compose当中一些常用的布局方式

常用布局

在日常开发中,铁定是少不了各种布局方式的,Jetpack compose也提供了相当多的布局方式,首先是横向布局Row。

Row

Row提供了横向布局的能力,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//Row
@Composable
fun RowTest() {
var nameList = listOf("1", "2", "3")
Row(
modifier = Modifier.padding(0.dp, 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
nameList.forEach() { name ->
//进行3等分,居中展示
Text(
text = "Row $name",
fontSize = TextUnit.Companion.Sp(15),
fontStyle = FontStyle.Normal,
maxLines = 2,
modifier = Modifier.background(
color = Color.LightGray,
shape = RoundedCornerShape(20)
).padding(10.dp)
.weight(0.3f),
textAlign = TextAlign.Center
);
}
}
}

效果如下所示:
row_demo
这里使用了weight属性进行等分,类似于LinearLayout的weight属性

Column

Column提供了纵向布局的能力,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Composable
fun ColumnTest() {
var nameList = listOf("1", "2", "3")
Column(
modifier = Modifier.padding(0.dp, 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
nameList.forEach { name ->
Text(
text = "Column $name",
fontSize = TextUnit.Companion.Sp(15),
fontStyle = FontStyle.Italic,
maxLines = 2,
modifier = Modifier.background(
color = Color.LightGray,
shape = RoundedCornerShape(20)
).padding(10.dp)
);
}
}
}

效果如下所示:

column_demo

Box

Box提供了叠放的效果,类似于FrameLayout:

1
2
3
4
5
6
7
8
9
10
11
12
13
//Stack Box
@Composable
fun StackDemo() {
Box(modifier = Modifier.padding(0.dp, 16.dp)) {
Text(
text = "layer one",
fontSize = TextUnit.Companion.Sp(30),
color = Color.Blue,
fontStyle = FontStyle.Italic
)
Text(text = "layer two", fontSize = TextUnit.Companion.Sp(10))
}
}

效果如下所示:
box_demo

滚动列表

使用ScrollableRow或ScrollableColumn可使Row或Column内的元素滚动,来看下ScrollableColumn的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Artist {
var avatarImg: Int = R.drawable.icon;
var name: String? = null;
var intro: String? = null;
var img: Int = R.drawable.img;
}

@Composable
fun ArtistCard(artist: Artist, onSelected: (Artist) -> Unit) {
Column(
Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable(onClick = { onSelected(artist) })
) {
Row() {
val avatarImg = imageResource(artist.avatarImg)
Image(
avatarImg,
modifier = Modifier.background(
color = Color.Transparent,
shape = RoundedCornerShape(20.dp)
)
.width(60.dp).height(60.dp)
)
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
artist.name?.let { Text(text = it) }
artist.intro?.let {
Text(text = it, modifier = Modifier.padding(0.dp, 8.dp, 0.dp, 0.dp))
}
}
}
val img = imageResource(artist.img)
Image(
img, modifier = Modifier.height(250.dp).padding(0.dp, 16.dp, 0.dp, 0.dp)
.background(color = Color.LightGray, shape = RoundedCornerShape(20.dp))
.fillMaxWidth()
)
}
}

@Composable
fun Feed(
feedItems: List<Artist>,
onSelected: (Artist) -> Unit
) {
//ScrollableColumn
ScrollableColumn(Modifier.fillMaxSize()) {
feedItems.forEach {
ArtistCard(it, onSelected)
}
}
}

@Composable
private fun ListDemo() {
val artistOne = Artist()
artistOne.name = "张三"
artistOne.intro = "哈哈哈哈"
artistOne.avatarImg = R.drawable.icon
artistOne.img = R.drawable.img

val artistTwo = Artist()
artistTwo.name = "张三"
artistTwo.intro = "哈哈哈哈"
artistTwo.avatarImg = R.drawable.icon
artistTwo.img = R.drawable.img

val artistThree = Artist()
artistThree.name = "张三"
artistThree.intro = "哈哈哈哈"
artistThree.avatarImg = R.drawable.icon
artistThree.img = R.drawable.img

Feed(feedItems = listOf(artistOne, artistTwo, artistThree), onSelected = {
// Toast.makeText(this, it.name, Toast.LENGTH_SHORT).show();
})

效果如下所示:
scrollable_column_demo

这个ScrollableColumn就类似于Android的ScrollView,如果要显示的元素很少,这种方法效果很好,但对于大型数据集,很快就会出现性能问题。如需仅显示屏幕上可见的部分元素,可以使用LazyColumnFor或LazyRowFor:

1
2
3
4
5
6
7
8
9
10
11
@Composable
fun Feed(
feedItems: List<Artist>,
onSelected: (Artist) -> Unit
) {
Surface(Modifier.fillMaxSize()) {
LazyColumnFor(feedItems) { item ->
ArtistCard(item, onSelected(item))
}
}
}

总结

以上只是Jetpack Compose的一个简单使用,还有很多高级的知识点没有涉及,包括ConstraintLayout、自定义布局、内置 Material组件、动画、主题,状态以及框架的实现原理等,如下所示:

compose_pack

感兴趣的同学可以花时间进行深入研究一番,应该会有收获。

总体使用下来的感受的话,结合kotlin搭配使用还是挺灵活的,第一次接触这种声明式UI的写法,还是觉得挺新奇的,做为一个新的知识点还是值得学习一下的~~~。

官方Sample里面有很多优秀的案例可以参考:
https://github.com/android/compose-samples

参考:
https://developer.android.com/jetpack/compose
https://developer.android.com/jetpack/compose/layout
https://developer.android.com/jetpack/compose/mental-model