Jetpack Compose 是用于构建原生 Android 界面的新工具包,无需修改任何 XML 布局,也不需要使用布局编辑器。相反,只需调用可组合函数来定义所需的元素,Compose 编译器即会完成后面的所有工作。
简而言之,使用Compose,不再需要xml编写页面。
可组合函数(Composable function)
Compose是围绕可组合函数构建的,只需要描述应用界面的外观并提供数据依赖,而不必关注界面的构建过程(如初始化元素、将其附加到父项等)。而创建Composable function,只需要添加注解@Composable到函数名称前。
首先,我们构建创建一个应用:ComposeTutorial。在AS中选择Empty Activity创建。
添加文本元素
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {//在此处定义布局
//此处的Text方法是由Compose界面库定义的文本Composable function
Text("Hello world!")
}
}
}
setContent块定义了activity的布局,在此处我们添加了Text即“Hello World!”。
自定义可组合函数
如果需要将一个函数转换为Composable function,我们需要添加注解“@Composable”。
修改MainActivity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MessageCard("Android")//使用我们自定义的Composable function
}
}
}
@Composable//添加注解,使该函数成为Composable function
fun MessageCard(name: String) {
Text(text = "Hello $name!")
}
在AS中预览Composable function
借助@Preview注解,可以在AS中预览Composable function,无需安装到设备或虚拟器中。
唯一要求是该注解不能用于接收参数的函数中,因此在MainActivity新增如下函数:
@Preview
@Composable
fun PreviewMessageCard() {
MessageCard("Android")
}
在重新构建后,该函数没有被调用,应用本身不会改变,但是AS对于所有添加了@Preview注解的界面元素可以进行预览,点击如下两个按钮之一即可:
布局(Layout)
在此处我们实现一个简单的聊天界面,显示发送者和消息内容,点击消息时可缩放。
添加多个文本
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//向Composable function中传入发送者名称和消息内容
MessageCard(Message("Android", "Jetpack Compose"))
}
}
}
//新建一个Message类,包含消息发送者和消息内容
data class Message(val author: String, val body: String)
@Composable
fun MessageCard(msg: Message) {
Text(text = msg.author)
Text(text = msg.body)
}
@Preview
@Composable
fun PreviewMessageCard() {
MessageCard(
msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
)
}
这段代码会在内容视图中创建两个文本元素。不过,由于未提供有关如何排列这两个文本元素的信息,因此它们会相互重叠,使文本无法阅读。
使用Column、Row、Box
Column,直译为“圆柱体、长列”。
使用该函数修改MessageCard,可以垂直排列元素,使其不再重叠文本。
@Composable
fun MessageCard(msg: Message) {
Column {
Text(text = msg.author)
Text(text = msg.body)
}
}
同样的,可以使用Row函数水平排列元素,而使用Box函数可以堆叠元素。
添加图片元素
使用Resource Manager从照片库中导入图片,修改MessageCard:
@Composable
fun MessageCard(msg: Message) {
//使用Row方法,水平排列图片和消息
Row {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = "Contact profile picture",
)
Column {
Text(text = msg.author)
Text(text = msg.body)
}
}
}
配置布局(Modifier)
使用修饰符(Modifier)实现。
Compose中的每一个组件都具有Modifier属性,通过他我们可以设置组件的大小、间距、外观,甚至添加互动事件,如点击、触摸事件。
@Composable
fun MessageCard(msg: Message) {
//在消息周围添加8dp的距离
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = "Contact profile picture",
modifier = Modifier
//设置图片大小
.size(40.dp)
//将图片修剪为圆形
.clip(CircleShape)
)
//在图片和消息之间添加一个水平的空间(Spacer),间距为8dp
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(text = msg.author)
// 在发送者和消息内容之间添加一个垂直的空间
Spacer(modifier = Modifier.height(4.dp))
Text(text = msg.body)
}
}
}
Material Design
Compose 旨在支持 Material Design 原则。它的许多界面元素都原生支持 Material Design。
使用
Jetpack Compose 原生提供 Material Design 3 及其界面元素的实现。我们使用 Material Design 样式改进MessageCard可组合项的外观。
在创建ComposeTutorial项目时,会同时创建一个名为“ComposeTutorialTheme”的Material主题,和一个来自Material Design 3的“Surface”。我们将用到这两位来封装MessageCard函数。
Material Design是围绕Color、Typography、Shape来构建的,我们将逐一添加这些元素。
修改MainActivity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//使用ComposeTutorialTheme和Surface封装MessageCard函数
ComposeTutorialTheme {
//将给定的组件或布局填满父容器的界面
Surface(modifier = Modifier.fillMaxSize()) {
MessageCard(Message("Android", "Jetpack Compose"))
}
}
}
}
}
@Preview
@Composable
fun PreviewMessageCard() {
//在@Preview中也要封装,沿用应用主题中定义的样式,保持统一
ComposeTutorialTheme {
Surface {
MessageCard(
msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!")
)
}
}
}
颜色
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = null,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
//为图像增加一个圆框
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(
text = msg.author,
//通过MaterialTheme.colorScheme,使用已封装主题中的颜色设置样式
color = MaterialTheme.colorScheme.secondary
)
Spacer(modifier = Modifier.height(4.dp))
Text(text = msg.body)
}
}
}
Typography(排版)
MaterialTheme
中提供了 Material Typography 样式,只需将其添加到 Text
可组合项中即可。
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = null,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(
text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = msg.body,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
Shape(形状)
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = null,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(
text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall
)
Spacer(modifier = Modifier.height(4.dp))
//将消息详情封装在Surface中,此时可自定义消息详情的大小、形状等布局
Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
Text(
text = msg.body,
modifier = Modifier.padding(all = 4.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
启用深色主题
又称夜间模式。由于支持Material Design,Jetpack Compose默认能够处理深色主题。
使用Material Design颜色、文本和背景时,系统会自动适应深色背景。
//可以在文件中以单独函数的形式创建多个预览,也可以向同一个函数中添加多个注解
@Preview(name = "Light Mode")
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
ComposeTutorialTheme {
Surface {
MessageCard(
msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
)
}
}
}
列表和动画
创建消息列表(LazyColumn、LazyRow)
LazyColumn、LazyRow是Compose常见的两个组件,他们的优点是可以延迟加载。他们只会加载当前可见的列表项,并在滚动时动态回收其他项,这使得他们适用于展示大量数据或无限滚动的列表。
在使用中,会包含一个items子项。他接受LIst作为参数,并且其lambda会接收到参数。系统会针对提供的List的每个项调用此lambda。
@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
//items子项
items(messages) { message ->
MessageCard(message)
}
}
}
@Preview
@Composable
fun PreviewConversation() {
ComposeTutorialTheme {
Conversation(SampleData.conversationSample)
}
}
在展开消息时显示动画效果
为了存储某条消息是否已展开,我们使用remember和mutableStateOf函数。
可组合函数可以使用remember将本地状态存储到内存中,并跟踪传递给mutableStateOf的值的变化。而mutableStateOf函数可以在可组合函数内部创建一个可变的状态,并将其与UI组件进行绑定。当状态值发生变化时,Compose会自动重绘相关的组件。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeTutorialTheme {
Conversation(SampleData.conversationSample)
}
}
}
}
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = null,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
// 在此变量中,跟踪当前消息是否展开
var isExpanded by remember { mutableStateOf(false) }
// 当点击该消息时,改变展开状态
Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
Text(
text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall
)
Spacer(modifier = Modifier.height(4.dp))
Surface(
shape = MaterialTheme.shapes.medium,
shadowElevation = 1.dp,
) {
Text(
text = msg.body,
modifier = Modifier.padding(all = 4.dp),
// 若展开,全部显示;不展开,最大显示一行
maxLines = if (isExpanded) Int.MAX_VALUE else 1,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
注:需要添加以下导入内容才能正确使用 Kotlin 的委托属性语法(
by
关键字):import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
使用by关键字是避免每次都访问value属性的好方法。
同时需要注意的是,我们应该只在可组合函数的作用域之外改变状态。这是因为可组合项可以频繁运行、且以任何顺序执行。此处我们可以在clickable中修改isExpanded的值,是因为clickable不是可组合函数。
除了remember关键字,我们还可以使用rememberSaveable,它与前者类似,但存储的值可以在重新创建activity和进程后保存下来。需要注意的是,rememberSaveable适用于UI状态,如购物车的商品数量或选定的标签页,但不适用于过渡动画状态等。
同时我们可以加入颜色,在消息缩放时改变消息的颜色。但不能只是简单的改变消息的背景颜色,我们应当加入动画,使得变化时逐步更改。
相同的,在点击缩放时,我们也可以加上动画,使得缩放更加顺滑。
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = null,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
var isExpanded by remember { mutableStateOf(false) }
// 该变量会逐步更新颜色,animateColorAsState函数实现颜色之间的过渡动画效果
val surfaceColor by animateColorAsState(
if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
)
Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
Text(
text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall
)
Spacer(modifier = Modifier.height(4.dp))
Surface(
shape = MaterialTheme.shapes.medium,
shadowElevation = 1.dp,
color = surfaceColor,
//加入animateContentSize,给缩放加入动画,更加顺滑
modifier = Modifier.animateContentSize().padding(1.dp)
) {
Text(
text = msg.body,
modifier = Modifier.padding(all = 4.dp),
maxLines = if (isExpanded) Int.MAX_VALUE else 1,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}