【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画

文章目录

  • 一、JetpackCompose 编程思想
    • 1.1 Jetpack Compose 是什么
    • 1.2 案例说 Jetpack Compose 的优势
    • 1.3 和 Flutter 的区别
  • 二、@Compose 可组合函数
    • 2.1 @Preview 预览函数
  • 三、布局
    • 3.1 添加多个文本
    • 3.2 Column 和 Row 函数
    • 3.3 添加图片元素
    • 3.4 配置布局
  • 四、Material Design
    • 4.1 使用 Material Design 样式
    • 4.2 Color 颜色
    • 4.3 Typography 排版
    • 4.4 Shape 形状
    • 4.5 启用深色主题
  • 五、列表和动画
    • 5.1 创建消息列表
    • 5.1 展开消息时显示动画效果

一、JetpackCompose 编程思想

1.1 Jetpack Compose 是什么

Jetpack Compose 是用于构建原生 Android 界面的库。它使用更少的代码、直观的 Kotlin API,可以简化 Android 界面开发。

  • 更少的代码:只需要写Kotlin,而不需要写XML布局了,大约只需要1/10的代码量,代码集中在一个文件也就更容易维护。
  • 直观:只需要声明式的函数即可,Compose 编译器会帮我们编译成布局文件。可以构建小型可复用的组件。
  • 兼容:和老代码兼容:View 可以调用 Compose,Compose 也可以调用 View,其他库(如Navigation、ViewModel、Kotlin的协程)都可以用 Compose。

1.2 案例说 Jetpack Compose 的优势

其实就是从过程式,变为声明式。

举例说明,假如我们要实现一个答题的APP,当答完一个题后,才能继续答下一个题,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第1张图片

我们聚焦到一个列表项,其由图片、文本、按钮组成,示例如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第2张图片

如果用过程式的话,需要先写 xml 文件,再在 Activity、Fragment 中通过 findViewById() 引用变量,再通过 setImage()、setText() 设置图文内容,并设置 Button 的 onClickListener(),还要考虑到屏幕旋转等需求,非常繁琐,示例如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第3张图片

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第4张图片

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第5张图片

而如果用 Compose 这种声明式语法的话,只需要写 Kotlin 这一种文件即可,代码如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第6张图片

在 Compose 中,UI 控件都是 functions,而不是 objects,UI 控件的内容都是由变量的 state 来自动渲染的,就像前端 Vue 里的 v-if 根据变量来渲染组件的双向绑定一样。

当 UI 控件变化时(如 Button 被点击),UI 控件会向 Event Handler 发送 Event,而 Event Handler 会决定是否需更新 UI 件的 state,当 UI 控件的 state 变化时,依赖于他的 functions 或 UI 控件就会重绘,示例如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第7张图片

所以,在 Compose 中,怎么实现点击事件呢?我们定义变量(本例中为 selected,当点击时反转其值即可),代码如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第8张图片

1.3 和 Flutter 的区别

JetpackCompose 和 Flutter 二者都是基于 Skia 渲染引擎发展的框架,最终也会殊途同归:都变成跨平台的 UI 框架。

  • Flutter 发展较早,有先发优势,特点是跨平台,其技术也很大胆:Dart 语言,目前闲鱼技术公众号经常能看到对其深度应用。
  • JetpackCompose 发展较晚,是 Jetpack 库中的一员,和原生 Android 技术栈较吻合,最终也会成为跨端方案。

二、@Compose 可组合函数

JetpackCompose 是用 @Compose 可组合函数 构建的,这些函数可以描述UI和依赖的数据,不需关注界面的构建过程(初始化元素、添加到父级布局等)。

首先,新建 ComposeTest 项目,新建项目时选择 Empty Compose Activity 类型,示例如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第9张图片

首先,在 MainActivity 的 onCreate() 中,用 setContent() 定义 activity 的布局,在其中调用可组合函数,代码如下:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}

运行后,显示一行文本,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第10张图片

通过在函数前写 @Composable 注解,可使函数成为可组合函数,代码如下:

import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

运行后,同样显示一行文本,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第11张图片

2.1 @Preview 预览函数

可以添加 @Preview 注解,代码如下:

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}

在不运行 App 的情况下,也可在 Android Studio 中预览,并且实时刷新,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第12张图片

三、布局

布局是多层次的,每个 @Compose 函数中,又包含其他 @Compose 函数,就像前端 Vue 的父组件和子组件一样。

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第13张图片

3.1 添加多个文本

我们再定义一个名为 Message 的 data class,然后分别在 setContent 和 @Preview 中添加两个文本,代码如下:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "JetpackCompose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: com.bignerdranch.android.composetest.Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

运行后,文本重叠了,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第14张图片

3.2 Column 和 Row 函数

Column 函数可以垂直排列元素,Row 函数可以水平排列元素,代码如下:

import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

运行后,文字即不重叠了,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第15张图片

3.3 添加图片元素

可以使用 Resource Manager 从照片库中导入图片,或使用这张图片,添加 Row,代码如下:

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.res.painterResource

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}

运行后,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第16张图片

3.4 配置布局

可以通过 Spacer() 留白,通过 Row() 和 Column() 的组合实现复杂的布局,代码如下:

import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(msg: com.bignerdranch.android.composetest.Message) {
    // 添加 padding
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_foreground),
            contentDescription = "联系人头像",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))
        Column {
            Text(text = msg.author)
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第17张图片

四、Material Design

Compose 的很多界面元素,都支持 Material Design 风格,本节我们使用 Material Design widget 来设计应用的样式。

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第18张图片

4.1 使用 Material Design 样式

在 ui.theme/Theme.kt 中可看到 Android Studio 默认生成的主题,主题如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第19张图片

首先,用项目默认的 ComposeTestTheme 主题 和 Surface 主题来封装 MessageCard 函数(在 @Preview 和 setContent 中均需执行此操作),代码如下:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.bignerdranch.android.composetest.ui.theme.ComposeTestTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTestTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: com.bignerdranch.android.composetest.Message) {
    // 添加 padding
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_foreground),
            contentDescription = "联系人头像",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))
        Column {
            Text(text = msg.author)
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTestTheme {
        Surface() {
            MessageCard(Message("Colleague", "Take a look at Jetpack Compose, it's great!"))
        }
    }
}

运行后,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第20张图片

4.2 Color 颜色

通过 MaterialTheme.colors 可以封装主题的颜色,通过 .border() 可以设置边框,代码如下:

@Composable
fun MessageCard(msg: Message) {
    // 添加 padding
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_foreground),
            contentDescription = "联系人头像",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))
        Column {
            Text(text = msg.author, color = MaterialTheme.colors.secondaryVariant)
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}

添加颜色和边框后,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第21张图片

4.3 Typography 排版

MaterialTheme 提供了 Matetial Typography 样式,在 Text() 中设置 style 即可,代码如下:

@Composable
fun MessageCard(msg: Message) {
    // 添加 padding
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_foreground),
            contentDescription = "联系人头像",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))
        Column {
            Text(text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2)
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body, style = MaterialTheme.typography.body2)
        }
    }
}

运行后,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第22张图片

4.4 Shape 形状

将消息封装在 Surface 可组合项中,可以自定义消息的形状、高度,设置内边距,代码如下:

@Composable
fun MessageCard(msg: Message) {
    // 添加 padding
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_foreground),
            contentDescription = "联系人头像",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))
        Column {
            Text(text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2)
            Spacer(modifier = Modifier.height(4.dp))
            Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) {
                Text(text = msg.body, modifier = Modifier.padding(all = 4.dp), style = MaterialTheme.typography.body2)
            }
        }
    }
}

运行后,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第23张图片

4.5 启用深色主题

通过 uiMode 参数,可设置深色主题,代码如下:

@Preview(name = "Light Mode")
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode")
@Composable
fun PreviewMessageCard() {
    ComposeTestTheme {
        Surface() {
            MessageCard(Message("Colleague", "Take a look at Jetpack Compose, it's great!"))
        }
    }
}

设置手机为深色主题后,运行 App 后即显示了深色效果,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第24张图片

五、列表和动画

只有一个消息太单调了,我们希望显示消息列表。本节通过 Jetpack Compose 添加列表和动画,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第25张图片

5.1 创建消息列表

LazyColumn 和 LazyRow 会自动折行来适配屏幕宽度,在下例中我们定义了名为 SampleData 的 List,将其通过 LazyColumn 传入 MessageCard,来显示布局,代码如下:

@Composable
fun Conversation(message: List<Message>) {
    LazyColumn {
        items(message) { message -> MessageCard(message) }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTestTheme {
        Conversation(SampleData.conversationSample)
    }
}


object SampleData {
    // Sample conversation data
    val conversationSample = listOf(
        Message(
            "Colleague",
            "Test...Test...Test..."
        ),
        Message(
            "Colleague",
            "List of Android versions:\n" +
                    "Android KitKat (API 19)\n" +
                    "Android Lollipop (API 21)\n" +
                    "Android Marshmallow (API 23)\n" +
                    "Android Nougat (API 24)\n" +
                    "Android Oreo (API 26)\n" +
                    "Android Pie (API 28)\n" +
                    "Android 10 (API 29)\n" +
                    "Android 11 (API 30)\n" +
                    "Android 12 (API 31)\n"
        ),
        Message(
            "Colleague",
            "I think Kotlin is my favorite programming language.\n" +
                    "It's so much fun!"
        ),
        Message(
            "Colleague",
            "Searching for alternatives to XML layouts..."
        ),
        Message(
            "Colleague",
            "Hey, take a look at Jetpack Compose, it's great!\n" +
                    "It's the Android's modern toolkit for building native UI." +
                    "It simplifies and accelerates UI development on Android." +
                    "Less code, powerful tools, and intuitive Kotlin APIs :)"
        ),
        Message(
            "Colleague",
            "It's available from API 21+ :)"
        ),
        Message(
            "Colleague",
            "Writing Kotlin for UI seems so natural, Compose where have you been all my life?"
        ),
        Message(
            "Colleague",
            "Android Studio next version's name is Arctic Fox"
        ),
        Message(
            "Colleague",
            "Android Studio Arctic Fox tooling for Compose is top notch ^_^"
        ),
        Message(
            "Colleague",
            "I didn't know you can now run the emulator directly from Android Studio"
        ),
        Message(
            "Colleague",
            "Compose Previews are great to check quickly how a composable layout looks like"
        ),
        Message(
            "Colleague",
            "Previews are also interactive after enabling the experimental setting"
        ),
        Message(
            "Colleague",
            "Have you tried writing build.gradle with KTS?"
        ),
    )
}

运行后,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第26张图片

5.1 展开消息时显示动画效果

为了记录消息是否已展开,可以用 remember 将本地状态存储到内存中,并跟踪传递给 mutableStateOf 的值的变化,当值更新时,系统会自动重绘使用此值的 @Composable,这称为重组。

我们添加 isExpand 变量,并当点击时反转此变量的值,当变量改变时,即会重绘UI,设置的maxLines 就会生效,代码如下:

@Composable
fun MessageCard(msg: Message) {
    // 添加 padding
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_foreground),
            contentDescription = "联系人头像",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colors.secondary, 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.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2)
            Spacer(modifier = Modifier.height(4.dp))
            Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.body2
                )
            }
        }
    }
}

运行后,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第27张图片

接下来,我们还可以设置动画的颜色,让颜色在 MaterialTheme.colors.surface 和 MaterialTheme.colors.primary 之间切换,我们通过 animateColorAsState() 函数来切换颜色,并通过 animateContentSize() 函数来为消息容器的大小添加动画效果,代码如下:

@Composable
fun MessageCard(msg: Message) {
    // 添加 padding
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_foreground),
            contentDescription = "联系人头像",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))
        // 跟踪这个变量
        var isExpanded by remember { mutableStateOf(false) }
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface
        )
        // 点击时,切换这个变量的值
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2)
            Spacer(modifier = Modifier.height(4.dp))
            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 1.dp,
                color = surfaceColor,
                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.body2
                )
            }
        }
    }
}

运行后,效果如下:

【Android-JetpackCompose】1、实战聊天界面:@Compose 可组合函数、用 Row() 和 Column() 设计布局、MaterialDesign 样式、列表、动画_第28张图片

你可能感兴趣的:(android,android,jetpack,kotlin)