前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >安卓软件开发:Jetpack Compose、Material 3和Kotlin协程在Android开发协程App

安卓软件开发:Jetpack Compose、Material 3和Kotlin协程在Android开发协程App

原创
作者头像
Nimyears
修改2024-09-19 20:56:27
4910
修改2024-09-19 20:56:27
举报
文章被收录于专栏:JetpackCompose M3

2024年已经过半了,我作为聋人独立开发者,我经常反思自己在这半年中的成长,自己这半年到底进步了多少?在这篇文章里,我分享一个用Jetpack Compose、Material 3和Kotlin协程开发NimTwoTrackApp的案例。如果你有一定开发经验,相信这篇文章对你会非常有所帮助。

一、项目背景

NimTwoTrackApp模拟两位选手赛跑。应用界面中包含两个按钮:开始/停止和重置,两个用于显示赛跑者进度的进度条。选手 1 和 2 被设置为不同的速度“奔跑”。


二、项目开发

这项目使用 Jetpack Compose 进行 UI 构建,结合 Material 3 设计元素实现了简洁美观UI。Kotlin 协程处理并发任务,两个者多个选手的进度同步更新而不阻塞主线程。

PS:适合已有编程基础的开发者,如果你是初学者,建议先看看我另一篇基础文章:安卓软件开发-手把教讲解Kotlin协程-腾讯云开发者社区-腾讯云 (tencent.com)

2.1 构建 UI(这部分不做详细介绍,这是最基础的,具体文档请参考官方网站:https://m3.material.io/

代码语言:java
复制
@Composable
fun NimTwoTrackScreen(
    playerOne: RaceParticipant,
    playerTwo: RaceParticipant,
    isRunning: Boolean,
    onRunStateChange: (Boolean) -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        modifier = modifier,
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = stringResource(R.string.run_a_race),
            style = MaterialTheme.typography.headlineSmall,
        )
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(dimensionResource(R.dimen.padding_medium)),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            StatusIndicator(
              participantName = playerTwo.name,
                currentProgress = playerTwo.currentProgress,
                maxProgress = stringResource(
                    R.string.progress_percentage,
                    playerTwo.maxProgress
                ),
                progressFactor = playerTwo.progressFactor,
                modifier = Modifier.fillMaxWidth(),
            )
            Spacer(modifier = Modifier.size(dimensionResource(R.dimen.padding_large)))
            StatusIndicator(
                participantName = playerTwo.name,
                currentProgress = playerTwo.currentProgress,
                maxProgress = stringResource(
                    R.string.progress_percentage,
                    playerTwo.maxProgress
                ),
                progressFactor = playerTwo.progressFactor,
                modifier = Modifier.fillMaxWidth(),
            )
            Spacer(modifier = Modifier.size(dimensionResource(R.dimen.padding_large)))
            RaceControls(
               isRunning = isRunning,
                onRunStateChange = onRunStateChange,
                onReset = {
                    playerOne.reset()
                    playerTwo.reset()
                    onRunStateChange(false)
                },
                modifier = Modifier.fillMaxWidth(),
            )
        }
    }
}

2.1.1预览图

代码语言:javascript
复制
@Preview(showBackground = true)
@Composable
fun NimTwoTrackAppPreview() {
    AppTheme {
        NimTwoTrackApp()
    }
}

2.2 开发StatusIndicator 组件

StatusIndicator 用于显示每个选手的当前进度,通过进度条和文本实时反映选手状态。

代码语言:java
复制
@Composable
private fun StatusIndicator(
    participantName: String,
    currentProgress: Int,
    maxProgress: String,
    progressFactor: Float,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier
    ) {
        Text(
            text = participantName,
            modifier = Modifier.padding(end = dimensionResource(R.dimen.padding_small))
        )
        Column(
            verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small))
        ) {
            LinearProgressIndicator(
                progress = progressFactor,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(dimensionResource(R.dimen.progress_indicator_height))
                    .clip(RoundedCornerShape(dimensionResource(R.dimen.progress_indicator_corner_radius)))
            )
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Text(
                    text = stringResource(R.string.progress_percentage, currentProgress),
                    textAlign = TextAlign.Start,
                    modifier = Modifier.weight(1f)
                )
                Text(
                    text = maxProgress,
                    textAlign = TextAlign.End,
                    modifier = Modifier.weight(1f)
                )
            }
        }
    }
}

2.3 开发RaceControls 组件

RaceControls 提供了比赛的控制功能,包括开始、暂停和重置。比赛的控制由 onRunStateChangeonReset 函数实现。

代码语言:java
复制
@Composable
private fun RaceControls(
    onRunStateChange: (Boolean) -> Unit,
    onReset: () -> Unit,
    modifier: Modifier = Modifier,
    isRunning: Boolean = true,
) {
    Column(
        modifier = modifier.padding(top = dimensionResource(R.dimen.padding_medium)),
        verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium))
    ) {
        Button(
            onClick = { onRunStateChange(!isRunning) },
            modifier = Modifier.fillMaxWidth(),
        ) {
            Text(if (isRunning) stringResource(R.string.pause) else stringResource(R.string.start))
        }
        OutlinedButton(
            onClick = onReset,
            modifier = Modifier.fillMaxWidth(),
        ) {
            Text(stringResource(R.string.reset))
        }
    }
}

2.3.1 代码解析

  • RaceControls 提供按钮来控制比赛的状态(开始、暂停、重置),按钮点击事件通过回调函数控制状态变化。

2.4 开发主界面

NimTwoTrackApp 负责初始化启动界面,包含两个选手的进度条和比赛控制按钮。

代码语言:java
复制
@Composable
fun NimTwoTrackApp() {
    val playerOne = remember { RaceParticipant("NimPlayer1", 1) }
    val playerTwo = remember { RaceParticipant("NimPlayer2", 2) }
    var raceInProgress by remember { mutableStateOf(false) }

    if (raceInProgress) {
        LaunchedEffect(playerOne, playerTwo) {
            coroutineScope {
                launch { playerOne.run() }
                launch { playerTwo.run() }
            }
            raceInProgress = false
        }
    }
    NimTwoTrackScreen(playerOne, playerTwo, raceInProgress, onRunStateChange = { raceInProgress = it })
}

2.4.1 代码解析

  • NimTwoTrackApp 初始化两个选手对象,通过 LaunchedEffect 启动协程同时更新选手的进度。比赛状态由 raceInProgress 控制,当状态变更时,界面会响应。

2.5 开发选手状态管理

(1)RaceParticipant 类是每个选手的状态持有者,通过协程更新每个选手的进度。

代码语言:java
复制

package com.nim.nimTwoTrack.ui
class RaceParticipant(
    val name: String,
    val maxProgress: Int = 100,
    val progressDelayMillis: Long = 500L,
    private val progressIncrement: Int = 1,
    private val initialProgress: Int = 0
)
init {
    require(maxProgress > 0) { "maxProgress=$maxProgress; 必须大于0" }
    require(progressIncrement > 0) { "progressIncrement=$progressIncrement; 必须大于0" }
}
  • name:选手名字。
  • maxProgress:选手的最大进度值,默认是100,表示比赛结束的进度。
  • progressDelayMillis:每次进度增加之间的延迟时间,默认是500 毫秒,用于模拟跑步的速度。
  • progressIncrement:每次更新时进度增加的量,默认是1,表示每次进度的增量。
  • initialProgress:初始进度,默认是0,表示比赛开始时的进度。

(2)init{}

require 语句:这是一个简单输入验证,确保 maxProgressprogressIncrement 都是正数。如果传递了非法值,代码会抛出 IllegalArgumentException 输出相应的错误信息。

  • maxProgress:必须大于 0,否则比赛无法进行。
  • progressIncrement:也必须大于 0,否则进度无法前进。

(3) 选手的当前进度 (currentProgress)

代码语言:java
复制
var currentProgress by mutableStateOf(initialProgress)
    private set
  • currentProgress:用于跟踪选手的当前进度,通过 mutableStateOf 管理状态的变化。这是 Jetpack Compose 中常用的方式,通过 mutableStateOf 可以保证 UI 在状态改变时自动刷新。
  • private set:保证 currentProgress 只能在类的内部修改,外部不能直接更改它的值。

(4)run() 方法

代码语言:java
复制
suspend fun run() {
    while (currentProgress < maxProgress) {
        delay(progressDelayMillis)
        currentProgress += progressIncrement
    }
}
  • suspend:这是一个挂起函数,在协程中运行,允许异步执行不阻塞主线程。
  • while (currentProgress < maxProgress):循环条件确保赛跑者的进度在达到最大进度之前会不断增加。
  • delay(progressDelayMillis):延迟一段时间(默认500毫秒)后更新进度。delay 是一个挂起函数,会暂停当前协程但不会阻塞主线程。
  • currentProgress += progressIncrement:每次延迟之后,当前进度会根据设定的 progressIncrement 增加。

(5) reset() 方法

代码语言:java
复制
fun reset() {
    currentProgress = 0
}
  • reset():当前进度重置是 0。用于在用户点击“重置”按钮时,重置赛跑状态。

(6) progressFactor 属性

代码语言:java
复制
val RaceParticipant.progressFactor: Float
    get() = currentProgress / maxProgress.toFloat()
  • progressFactor:这是一个扩展属性,用于计算当前进度相对于最大进度的比例,值范围在 0 到 1 之间。这一比例用于更新进度条的显示。
  • currentProgress / maxProgress.toFloat():确保计算结果是浮点型,精确表示进度百分比。

2.6 应用入口

通过 setContent 初始化 UI,调用 NimTwoTrackApp 启动App的主界面。

代码语言:java
复制
    ...
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            enableEdgeToEdge()
            super.onCreate(savedInstanceState)
            setContent {
                AppTheme {
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                    ) {
                        //调用UI
                        NimTwoTrackApp()
                    }
                }
            }
        }
    }

2.7 跑通项目效果

三、测试

JUnitKotlin Coroutines Test 进行 单元测试。

3.1 TestProgressUpdated

代码语言:java
复制
@Test
fun TestProgressUpdated() = runTest {
    val expectedProgress = 1
    launch { raceParticipant.run() }
    advanceTimeBy(raceParticipant.progressDelayMillis)
    runCurrent()
    assertEquals(expectedProgress, raceParticipant.currentProgress)
}

3.1.1 代码解析

  • 测试验证了当 RaceParticipant 启动后,进度是否按预期增加。
  • 使用 advanceTimeBy 模拟时间流逝,协程更新进度,通过 assertEquals 检查当前进度是否等于预期值。

3.1.2 测试用例 1

  • 测试结果:选手在 500 毫秒内进度是1。
  • 测试结果:通过,预期进度为 1,实际进度是 1。

3.2 TestFinishedProgressUpdated

代码语言:java
复制
@Test
fun TestFinishedProgressUpdated() = runTest {
    launch { raceParticipant.run() }
    advanceTimeBy(raceParticipant.maxProgress * raceParticipant.progressDelayMillis)
    runCurrent()
    assertEquals(100, raceParticipant.currentProgress)
}

3.2.1 代码解析

  • 测试整个赛跑是否完成,检查赛跑结束时的进度是不是 100
  • 通过 advanceTimeBy 快进整个赛跑过程,验证终点的 currentProgress 值。

3.2.2 测试用例 2

  • 测试结果:选手在完成比赛时,最大进度是100。
  • 测试结果:通过,预期进度是100,实际进度是100。

3.3 TestPausedProgressUpdated

代码语言:java
复制
@Test
fun TestPausedProgressUpdated() = runTest {
    val expectedProgress = 5
    val racerJob = launch { raceParticipant.run() }
    advanceTimeBy(expectedProgress * raceParticipant.progressDelayMillis)
    runCurrent()
    racerJob.cancelAndJoin()
    assertEquals(expectedProgress, raceParticipant.currentProgress)
}

3.3.1 代码解析

  • 模拟了赛跑暂停的情况。在更新了几次进度后,通过 cancelAndJoin() 停止协程,验证暂停时的进度是不是正确。

3.3.2 测试用例 3

  • 测试结果:选手在 5 个增量后暂停,进度是 5。
  • 测试结果:通过,预期进度是 5,实际进度是 5。

3.4 TestPausedAndResumedProgressUpdated

代码语言:java
复制
@Test
fun TestPausedAndResumedProgressUpdated() = runTest {
    val expectedProgress = 5

    repeat(2) {
        val racerJob = launch { raceParticipant.run() }
        advanceTimeBy(expectedProgress * raceParticipant.progressDelayMillis)
        runCurrent()
        racerJob.cancelAndJoin()
    }

    assertEquals(expectedProgress * 2, raceParticipant.currentProgress)
}

3.4.1 代码解析

  • 模拟了暂停和恢复的赛跑过程。每次暂停时,选手的进度会保持,恢复后继续赛跑,最终验证进度是否按两次跑步的累加结果更新。

3.4.2 测试用例 4

  • 测试结果:选手两次运行后,进度是10(每次运行 5 个增量)。
  • 测试结果:通过,预期进度是10,实际进度是10。

四、视频演示

视频内容

五、技术难点

开发这个App时,我遇到了五个技术主要难点是

  1. 并发管理:保证两个选手的进度可以同时更新,不会引起界面卡顿。Kotlin协程可以做到了,发挥了重要作用,可以实现了非阻塞方式处理后台任务。
  2. 性能优化:在不牺牲性能的前提下实现平滑的动画和过渡效果。减少不必要的布局重排和重绘,优化资源加载提高App的响应速度。
  3. 状态管理:在Jetpack Compose中管理状态是一个非常大挑战,特别是涉及到多个组件和协程时,使用mutableStateOfremember保证状态的一致性和内存效率。
  4. 错误处理:处理错误和异常是非常关键,为了防止应用崩溃。我实现了全面的异常捕获和处理机制,保证了App的稳定性。
  5. 测试和验证:由于涉及到并发和状态变化,编写测试验证应用变得复杂。用了Kotlin的测试框架(Junit)和协程测试库。

六、学习技术笔记

  • 协程:它们就像后台任务的轻量级线程,在不阻塞主界面的情况下进行计算。
  • 挂起函数:这些函数可以在等待操作(比如网络请求或定时任务)时挂起,不会拖慢整个应用。
  • 协程构建器:比如launchasync,可以帮助App启动和管理协程。
  • 结构化并发:管理多个协程并保持代码整洁。

七、总结

通过这个项目,我对Jetpack Compose、Material 3和Kotlin协程的实用性有了更深的理解。这个UI框架让我快速构建了漂亮和高端UI,也处理了复杂的后台任务。虽然一开始有点复杂,真的可以做到了能让开发工作变得轻松很多,我意识到了测试非常重要,可以保证每个部分都能按预期工作。

有任何问题欢迎提问,感谢大家阅读 :)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、项目背景
  • 二、项目开发
    • 2.1 构建 UI(这部分不做详细介绍,这是最基础的,具体文档请参考官方网站:https://m3.material.io/)
      • 2.1.1预览图
        • 2.2 开发StatusIndicator 组件
          • 2.3 开发RaceControls 组件
            • 2.3.1 代码解析
              • 2.4 开发主界面
                • 2.4.1 代码解析
                  • 2.5 开发选手状态管理
                    • 2.6 应用入口
                      • 2.7 跑通项目效果
                      • 三、测试
                        • 3.1 TestProgressUpdated
                          • 3.1.1 代码解析
                            • 3.1.2 测试用例 1
                              • 3.2 TestFinishedProgressUpdated
                                • 3.2.1 代码解析
                                  • 3.2.2 测试用例 2
                                    • 3.3 TestPausedProgressUpdated
                                      • 3.3.1 代码解析
                                        • 3.3.2 测试用例 3
                                          • 3.4 TestPausedAndResumedProgressUpdated
                                            • 3.4.1 代码解析
                                              • 3.4.2 测试用例 4
                                              • 四、视频演示
                                              • 五、技术难点
                                              • 六、学习技术笔记
                                              • 七、总结
                                              相关产品与服务
                                              云开发 CloudBase
                                              云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
                                              领券
                                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档