准备知识
JetpackCompose 相比传统 XML+Java 的优势
-
不需要多个 XML 和 Activity
-
可组合函数负责更新和构建页面
-
不需要通过 ID 来查找视图,而是通过视图来绑定
-
与 Java+XML 的传统方式相兼容
使用 JetpackCompe 构建页面
在 Andorid Studio 中新建项目,选择 Empty Compose Activity 后点击 Next
-
需要 API 21+
-
只能使用 Kotlin 语言
AndoridManifest.xml 文件
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
|
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/my_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/my_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Profile"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Profile">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
|
application 内属性
-
android:icon
App 的主图标
-
android:label
应用名称
-
android:roundIcon
应用背景图标
-
android:theme
应用的主题
activity 内属性:
-
android:name
应用启动后的主页面
-
android:label
应用名称
-
android:theme
应用的主题
资源引用
-
[xxx]
为引用资源的路径
-
[xxxx]
为引用资源的名称,不包括文件后缀
如在 string.xml
内定义的应用名称:
1
2
3
|
<resources>
<string name="app_name">Candy\'s Profile</string>
</resources>
|
在其他位置的引用可以使用:
依赖项
在 build.gradle(:app)
(注:作用域是 app) 下的 dependencies
组为依赖项,可以在这里添加你需要使用的库
案例 1 个人页面
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
package com.candy.ui_test
import android.R.attr.text
import android.R.attr.top
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun ProfilePage()
{
Card( //卡片
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp), // 正确的写法 (适用于 Material 3)
// elevation = 8.dp, // 在 Material 2 中这是可以的
modifier = Modifier.height(600.dp) //设置卡片高度
// .fillMaxSize()
.padding(top = 150.dp, bottom = 50.dp, start = 16.dp, end = 16.dp)
)
{
Column( //一种列表布局
modifier = Modifier.fillMaxHeight()//先添加该属性,填满整个高度
.verticalScroll(rememberScrollState()), //列表垂直滚动 参数为记住列表滚动状态
horizontalAlignment = Alignment.CenterHorizontally, //列表水平居中
verticalArrangement = Arrangement.Center, //列表垂直居中 填满后生效
)
{
Image(
painter = painterResource(id = R.drawable.photo), //图片的画家,即为图片路径
contentDescription = "Candy", //图片的描述
modifier = Modifier.size(125.dp) //图片的尺寸
// .clip(CircleShape) //图片的纯圆修饰符
.clip(RoundedCornerShape(15)) //图片的圆角形状 单位int 50为纯圆
.border(
width = 1.dp, //图片的边框宽度
color = Color.White, //图片的边框颜色
shape = RoundedCornerShape(15) //图片的边框形状
), //图片的边框
contentScale = ContentScale.Crop //图片的裁剪方式
)
Text(
text = "Candy",
fontWeight = FontWeight.Bold, //字体加粗
fontSize = 30.sp, //字体大小
)
Text(
text = "@Candquarzy\uD83C\uDFF3\uFE0F\u200D⚧\uFE0F",
fontSize = 12.sp, //设置字体大小
)
Row(
horizontalArrangement = Arrangement.SpaceEvenly, //列表水平均匀排列
modifier = Modifier.fillMaxWidth() //填满整个宽度
.padding(16.dp) //添加外边距
)
{
ProfileData("5.28k", "粉丝") //调用函数
ProfileData("114", "关注") //调用函数
ProfileData("87", "动态") //调用函数
}
Row(
horizontalArrangement = Arrangement.SpaceEvenly, //列表水平均匀排列
modifier = Modifier.fillMaxWidth() //填满整个宽度
// .padding(16.dp) //添加外边距
)
{
ElevatedButton(
onClick = {
/* TODO */
})
{
Text(
text = "关注",
)
}
ElevatedButton(
onClick = {
/* TODO */
})
{
Text("私信")
}
}
}
}
}
@Composable
fun ProfileData(count: String, text: String) //函数 用于存储数据列表
{
Column(
horizontalAlignment = Alignment.CenterHorizontally, //列表水平居中
)
{
Text(
text = count,
fontWeight = FontWeight.SemiBold, //字体加粗
)
Text(
text = text,
)
}
}
@Preview(showBackground = true) //预览注解 + 显示背景
@Composable
fun ProfilePagePreview()
{
ProfilePage()
}
|
布局
Column 垂直布局
1
|
import androidx.compose.foundation.layout.Column
|
一种纵向的列表布局
参数
modifier 参数
Row 水平布局
1
|
import androidx.compose.foundation.layout.Row
|
一种纵向的列表布局
参数
modifier 参数
Card 卡片布局
1
|
import androidx.compose.material3.Card
|
一种卡片形式的布局
参数
modifier 参数
-
.fillMaxSize/Widht/Height()
列表填满父容器 (宽度 / 高度)
-
.padding([int.dp/sp])
外边距 (可以分开设置 top bottom start end)
-
.height([int.dp])
卡片高度
-
.weight([int.dp])
卡片宽度
-
.requiredHeight(Weight)
强制设置高度 / 宽度 (不受父高度影响强制设置)
约束布局
1
|
import androidx.constraintlayout.compose.ConstraintLayout
|
一种使用 ID 来绑定组件的布局,可以更方便的定义组件所在页面中的位置
而在这种布局下, horizontalAlignment
和 verticalArrangement
不再可用
要使用约束布局,需要在 build.gradle
中添加依赖项:
1
|
implementation ("androidx.constraintlayout:constraintlayout-compose:1.1.1")
|
截至写这篇文章的时候,最新的版本为 1.1.1
Constraintlayout | Jetpack | Android Developers
要使用约束布局,需要按以下方式:
-
使用 createRefs()
或 createRefFor()
为 ConstraintLayout
中的每个可组合项创建引用
-
约束条件使用 Modifier.constrainAs()
修饰符提供的,该修饰符将引用作为参数,然后指定其约束条件
-
约束条件使用 linkTo()
方法指定
-
parent
是一个屏幕位置,可用于指定对 ConstraintLayout
可组合项本身的约束条件
modifier 参数
由 .constrainAs([String id])
控制
该项目中所有参数均为所设定的控件 ID 其中,parent
为父级,可以理解为屏幕
使用 Dimension.wrapContent 需要导入
1
|
import androidx.constraintlayout.compose.Dimension
|
约束布局的解耦合
优势:有助于实现横屏和竖屏的切换
-
首先需要导入一个盒子约束容器
-
在其内部定义一个变量,通过判断来判断用户当前为横屏还是竖屏,接着设置约束方法函数
而在该约束容器内部的约束布局内的元素需要给 Modifier
注册.layoutId (“String”)
,并在约束方法中引用
比如导入
1
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
BoxWithConstraints
约束盒子容器
<待定
约束方法
创建一个保护的成员函数
<待定
Scaffold “脚手架” 布局
1
|
import androidx.compose.material3.Scaffold
|
Scaffold 是一种类似于框架一样的结构。它将界面的不同部分整合在一起,使其具有一致的外观和风格
Scaffold 会自动计算举例宽度,所以在该可组合函数内部要使用 innerPadding->
来传入边距
案例 (来自谷歌官方):
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
|
@Composable
fun ScaffoldExample() {
var presses by remember { mutableIntStateOf(0) }
Scaffold(
topBar = {
TopAppBar(
colors = topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
Text("Top app bar")
}
)
},
bottomBar = {
BottomAppBar(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary,
) {
Text(
modifier = Modifier
.fillMaxWidth(),
textAlign = TextAlign.Center,
text = "Bottom app bar",
)
}
},
floatingActionButton = {
FloatingActionButton(onClick = { presses++ }) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Text(
modifier = Modifier.padding(8.dp),
text =
"""
This is an example of a scaffold. It uses the Scaffold composable's parameters to create a screen with a simple top app bar, bottom app bar, and floating action button.
It also contains some basic inner content, such as this text.
You have pressed the floating action button $presses times.
""".trimIndent(),
)
}
}
}
|
官方文档: Scaffold - Jetpack Compose | Android Developers
LazyColumn / LazyRow “懒” 列 / 行布局
1
2
|
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
|
这是一个可以扩展长度的容器,在该容器内的元素必须是动态长度
需要使用 item lambda 等遍历容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
val itemsList = (0..5).toList()
val itemsIndexedList = listOf("A", "B", "C")
@Composable
LazyColum(
)
{
items(itemsList){
Text(
text = "Item is ${it}",
)
}
}
|
Box 盒子布局
1
|
import androidx.compose.foundation.layout.Box
|
可以层叠元素的盒子,类似于 HTML 中的 div 标签
控件
Text 文本组件
1
|
import androidx.compose.material3.Text
|
一种显示文本的文本组件
参数
Image 图像组件
1
|
import androidx.compose.foundation.Image
|
一种显示图像的组件
官方文档: 加载图片 | Jetpack Compose | Android Developers
modifier 参数
官方文档: 自定义图片 | Jetpack Compose | Android Developers
TextField 文本输入框
1
|
import androidx.compose.material3.TextField
|
一种用于让用户输入的文本框有多种样式可选
-
TextField
基础的默认的样式,样式为填充
-
OutlinedTextField
轮廓样式版本,无填充
-
SecureTextField
安全输入,用于输入密码等
-
value
输入框内的值
-
onValueChange
当输入框内容变更后执行
-
label
输入框提示值
以下是一个案例,当用户输入内容更新后将用户输入的内容输出在 UI 中
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
|
var name_state by remember {
mutableStateOf("")
//使用remember和mutableStateOf创建一个可变状态变量
}
var name: String by rememberSaveable {
//使用rememberSaveable可以在页面旋转时保存数据
mutableStateOf("")
//使用remember和mutableStateOf创建一个可变状态变量
//只有这样在变化时才会进行页面重组
}
Column(
horizontalAlignment = Alignment.CenterHorizontally, //水平对齐居中
verticalArrangement = Arrangement.Center, //垂直对齐居中
modifier = Modifier.fillMaxSize(), //填充满屏幕
) {
Text("Hello $name"); //文本组件 将name的值复制给文本
//当name的值改变时,文本会自动更新(刷新界面)
Spacer(modifier = Modifier.height(20.dp)) //间隔组件,设置高度为20dp
TextField(
//文本输入框
value = name_state,
//原本文本框是无状态的,
// 然后使用remember和mutableStateOf创建了一个
// 可变状态变量name_state 并分配给它
onValueChange = {
//当输入框的值改变时调用
name_state = it //更新状态
},
label = {
Text("请输入:")
}
)
Button(
onClick = {
name = name_state //点击按钮时将输入框的值赋给name
}
)
{
Text(text = "显示")
}
}
|
官方文档: 配置文本字段 Jetpack Compose | Android Developers
Spacer 空行分隔符
1
|
import androidx.compose.foundation.layout.Spacer
|
modifier 参数
-
.size
大小
-
.weight
空行的宽度
-
.height
空行的高度
Icon 图标组件
1
2
|
import androidx.compose.material3.Icon
import androidx.compose.material.icons.Icons //导入谷歌的图标类
|
-
imageVector
图标名 谷歌官方提供的图标都在 Icons.
类下
-
contentDescription
无障碍提示
-
tint
应用于 imageVector
, 如果提供了 Color.[name]
, 则不应用色调
NavigationBar 底部导航栏
1
|
import androidx.compose.material3.NavigationBar
|
NavigationBarItem 导航栏图标按钮
1
|
import androidx.compose.material3.NavigationBarItem
|
-
selected
是否选中
-
onClick
点击事件,常用于导航跳转
-
icon
按钮图标
-
label
文本内容
这俩常用在一起,用于给 Scaffold
添加 bottomBar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
NavigationBar(
)
{
items.forEach {
item ->
NavigationBarItem(
selected = false,
onClick = {
},
icon = {
Icon( )
},
label = {
Text( )
}
)
}
}
|
通常来说会将图标和文字放在别处
- 创建一个包
model
- 新建一个 kotlin 类
- 在类中创建对象,并在导航栏中引用
1
|
import androidx.compose.material3.IconButton
|
-
onClick
点击事件
-
enabled
启用按钮 (默认为 true)
-
colors
按钮的颜色
可以在内部设置 Icon
图标和文字
1
2
3
4
5
6
7
8
9
10
11
12
13
|
IconButton(
onClick = {
//点击事件
}
)
{
Icon(
//图标设置
)
Text(
//显示文本设置
)
}
|
Dialog 对话框组件
1
|
import androidx.compose.ui.window.Dialog
|
1
|
import androidx.compose.ui.window.DialogProperties //使用DialogProperties需要导入
|
AlertDialog 提醒对话框组件
1
|
import androidx.compose.material3.AlertDialog
|
这是一个便捷的 API,用于创建一个简易的对话框,如取消确认等
以下是谷歌官方案例:
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
|
@Composable
fun AlertDialogExample(
onDismissRequest: () -> Unit,
onConfirmation: () -> Unit,
dialogTitle: String,
dialogText: String,
icon: ImageVector,
) {
AlertDialog(
icon = {
Icon(icon, contentDescription = "Example Icon")
},
title = {
Text(text = dialogTitle)
},
text = {
Text(text = dialogText)
},
onDismissRequest = {
onDismissRequest()
},
confirmButton = {
TextButton(
onClick = {
onConfirmation()
}
) {
Text("Confirm")
}
},
dismissButton = {
TextButton(
onClick = {
onDismissRequest()
}
) {
Text("Dismiss")
}
}
)
}
|
官方文档: 对话框 | Jetpack Compose | Android Developers
xxxDivider 分割线组件
-
HorizontalDivider
水平分割线
-
VerticalDivider
垂直分割线
组件参数:
-
thickness
使用此参数指定分隔线的粗细
-
color
使用此参数指定分隔线的颜色
官方文档: 分隔线 | Jetpack Compose | Android Developers
Chip 条状标签
1
2
|
import androidx.compose.material3.Chip
import androidx.compose.material3.ChipDefaults
|
有多种样式可选
-
onClick
设定点击状态
-
label
设定显示文字 需要 Composable 函数
-
selected
是否选中
-
leadingIcon
定义菜单项中前方显示的图标 需要 Compose 函数
-
trailingIcon
定义菜单项中后方显示的图标 需要 Compose 函数
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
|
var select by remember { mutableStateOf(false) }
FilterChip(
onClick = {
select = !select
},
label = {
Text(
text = "about"
)
},
selected = select,
leadingIcon = {
if (select)
{
{
Icon(
/* ICON */
)
}
}
else
{
null
},
}
)
|
官方文档: 条状标签 | Jetpack Compose | Android Developers
-
expanded
菜单是否可见
-
onDismissRequest
处理菜单关闭
-
onClick
点击菜单项后执行
-
text
定义菜单项中显示的内容 需要 Compose 函数而不是 String
-
leadingIcon
定义菜单项中前方显示的图标 需要 Compose 函数
-
trailingIcon
定义菜单项中后方显示的图标 需要 Compose 函数
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
|
var menuExpanded by remember { mutableStateOf(false) }
IconButton(
onClick = {
menuExpanded = true
}
)
{
Icon(
/* ICON */
)
}
DropdownMenu(
expanded = menuExpanded,
onDismissRequest = {
menuExpanded = false
}
)
{
DropdownMenuItem(
onClick = {
/* TODO */
},
text = {
Text(
text = "关于"
)
},
leadingIcon = {
Icon(
/* ICON */
)
}
)
}
|
官方文档: 菜单 | Jetpack Compose | Android Developers
Modifier 修饰符
官方文档:
Compose 修饰符列表 | Jetpack Compose | Android Developers
状态
在 Jetpack 中,我们需要使用 remember
关键字来设置一个可变状态
可变状态
remember
remember
是一个 Composable 函数,它用于在重组过程中记住一个值。当一个 Composable 重组时, remember
会返回它在上次重组时记住的相同值。如果没有 remember
, 每次重组时,局部变量都会被重新初始化而导致状态丢失
若需使用 remember
需要导入:
1
2
3
|
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.remember
|
mutableStateOf
mutableStateOf
是一个函数,用于创建一个 MutableState
实例。其接收一个初始值,并返回一个可观察的对象。当 MutableState
的 value
改变时,所有读取该 value
的 Composable 都会被标记为需要重组
在函数体中, mutableStateOf
为创建一个可观察的 MutableState<T>
对象。当 MutableState
中的值改变时,Compose 会自动触发依赖该状态的可组合函数的重组
声明 MutableState
对象的方法有如下 (default 为默认值):
-
val mutableState = remember { mutableStateOf(default) }
-
var value by remember { mutableStateOf(default) }
-
val (value, setValue) = remember { mutableStateOf(default) }
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var name_state by remember {
mutableStateOf("") //使用remember和mutableStateOf创建一个可变状态变量
}
Text("Hello $name_state")
// 假设我们要设定让用户输入的内容更改到该Text上:
OutlinedTextField( //文本输入框
value = name_state,s
//原本文本框是无状态的,
// 然后我们使用remember和mutableStateOf创建了一个
// 可变状态变量name_state 并分配给它
onValueChange = {
//当输入框的值改变时调用
name_state = it //更新状态
},
label = {
Text("请输入:")
} //标签文本
)
|
官方文档: 状态和 Jetpack Compose | Android Developers
导航
导航 | App architecture | Android Developers