你的体重健康吗?用compose做一个BMI应用测试一下吧

简介: 早上发现几年前做的一个BMI显示盘有些问题,一想到Jetpack Compose Desktop做这个效果也不错,虽然不可能用这个去代替网页上的系统,但感觉效果肯定也是不错的,值得尝试一下。

Jetpack Compose Desktop依赖部分就不说了,可以参考前文用Jetpack Compose Desktop极简配置做一个Windows桌面时间显示器用Jetpack Compose Desktop做一个推箱子小游戏,还是默认的依赖,其实也就是一个compose.desktop.currentOs,然后首先还是修订下基本窗口,做个竖着的矩形:

fun main() = application {
    Window(
        onCloseRequest = ::exitApplication,
        state = rememberWindowState(
            size = DpSize(300.dp, 500.dp),
            position = WindowPosition.Aligned(Alignment.Center)
        ),
        title = "BMI指数"
    ) {
        app()
    }
}

然后在app()里画上主体部分,也就是一个居中的Box,里面用Colunm来排列两个TextField分别接受身高和体重:

@Composable
@Preview
fun app() {
    MaterialTheme {
        Box(
            modifier = Modifier.padding(20.dp).fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Column {
                TextField(value = "", onValueChange = {}, label = { Text("身高") })
                TextField(value = "", onValueChange = {}, label = { Text("体重") })
            }
        }
    }
}

然后就能看到这个效果了:

image.png

记录输入

然后就是要保存身高体重的数据,并且做验证,所以先定义两个状态变量:

var weight = remember { mutableStateOf("") }
var height = remember { mutableStateOf("") }

这里可以做个通用的方法generateInput()

@Composable
fun generateInput(label: String, model: MutableState<String>) {
    return TextField(
        value = model.value,
        //手机上要输入数字用这个keyboardOptions就行了,但桌面应用不行
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
        onValueChange = {
            //因为有可能输入法错误,把小数点输入成句号,所以做下替换,将“。”替换成“.”
            val number = it.replace('。', '.')
            //这个判断我是经过一定考虑的,可以清空文本框,但不应该输入0或者以0开头的数
            if (number.isNotBlank() && !number.matches(Regex("[1-9](\\d+)?(\\.(\\d+)?)?"))) {
                return@TextField
            }
            model.value = number
        },
        label = { Text(label) })
}

然后把刚才的两个TextField换成用generateInput()重新生成文本框:

Column {
    generateInput("身高", height)
    generateInput("体重", weight)
}

这样就能把数据保存起来并且可以验证是数字了。

计算BMI

这里要专门做一个方法计算,方法名可以就叫calcBMI()

fun calcBMI(height: String, weight: String): BigDecimal {
    if (weight.isEmpty() || height.isEmpty())
        return BigDecimal.ZERO
    val weight = BigDecimal(weight).setScale(2, RoundingMode.HALF_UP)
    val height = BigDecimal(height).setScale(2, RoundingMode.HALF_UP)
    if (weight < BigDecimal.ONE || height < BigDecimal.ONE)
        return BigDecimal.ZERO
    return (weight / (height / BigDecimal(100)).pow(2)).setScale(1, RoundingMode.HALF_UP)
}

这里可以重新定义weightheightkotlin的语法特性名称遮蔽(Name shadowed),可以了解一下,后面的计算看上去很清爽也要得益于kotlinBigDecimal提供了直接/的操作。
然后再在Column下面做一个Text()进行显示:

Text(
    modifier = Modifier.padding(10.dp),
    text = calcBMI(height.value, weight.value).toString(),
    style = TextStyle(fontSize = 21.sp)
)

这个文本其实才是整个窗口的核心,所以大小我特意调大一点点,然后就能看到效果了:

image.png

但仅仅是这样肯定不行,总不能让人背下BMI标准或者每次算了还去找个表对照吧,所以下面再做个图提供说明参考。

标准值参考图像制作

下面要放一个Canvas用来显示BMI指数以及一个列图,众所周知BMI分4个级别(偏瘦、正常、超重、肥胖),所以我们先把标签画上。然后还要再画个框套着,4个级别就是4个框,框之间应该留些空白,4个框就应该有3道空白,每个空白定位10

//这个TextMeasurer给字体用,每个字体都要有
val measurer = rememberTextMeasurer()
val standardArray = listOf("偏瘦 <=18.4", "正常 18.5 ~ 23.9", "超重 24.0 ~ 27.9", "肥胖 >= 28.0")
Canvas(
    modifier = Modifier.fillMaxSize()
) {
    //总体高度应该减去中间(3*10)的空白间隙
    val calcHeight = size.height - 30
    for (index in 0..3) {
        //计算好每块的Y轴定位,给文字和框定位用
        val offsetY = calcHeight / 4 * index + index * 10
        drawText(
            measurer,
            standardArray[index],
            style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold),
            topLeft = Offset(5f, offsetY + 5),
        )
        drawRect(
            color = Color.Blue,
            topLeft = Offset(0f, offsetY),
            size = Size(size.width, calcHeight / 4),
            style = Stroke(2f)
        )
    }
}

然后就能看到一个比较简单的效果:

image.png

然后需要再给每个区域填充不同的颜色,比喻说健康就是绿色,肥胖应该是橙色。但是框线还是需要保留的,不过drawRect没办法同时FillStroke,所以只能画两个rect,所以上面的代码可以进行如下修改:

val colorArray = listOf(Color(0xff93b5cf), Color(0xff20a162), Color(0xfffcc515), Color(0xfff86b1d))
Canvas(
    modifier = Modifier.fillMaxSize()
) {
    val calcHeight = size.height - 30
    for (index in 0..3) {
        val offsetY = calcHeight / 4 * index + index * 10
        drawRect(
            color = colorArray[index],
            topLeft = Offset(0f, offsetY),
            size = Size(size.width, calcHeight / 4),
            style = Fill
        )
        drawRect(
            color = Color.DarkGray,
            topLeft = Offset(0f, offsetY),
            size = Size(size.width, calcHeight / 4),
            style = Stroke(1f)
        )
        drawText(
            measurer,
            standardArray[index],
            style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.DarkGray),
            topLeft = Offset(5f, offsetY + 5),
        )
    }
}

为什么这里我把drawText挪下来了呢?其实就是因为被Fillrect遮住了,如果在rect画好之后再画字就没问题了,所以要把drawText挪下来,文字颜色也稍微设置了一下,效果就是这样:

image.png

为什么这里我把drawText挪下来了呢?其实就是因为被Fillrect遮住了,如果在rect画好之后再画字就没问题了,所以要把drawText挪下来,文字颜色也稍微设置了一下,效果就是这样:

image.png
接下来要做的就是让rect默认都透明一些,如果上面的BMI数值属于它的区间,那么就颜色加深,边框也加厚,这就跟上面整个联动起来了,中间那一行Text()的赋值也应该取出来便于后面的判断了:

val bmi = calcBMI(height.value, weight.value)
Text(
    modifier = Modifier.padding(10.dp),
    text = bmi.toString(),
    style = TextStyle(fontSize = 23.sp)
)
var selectIndex = if (bmi == BigDecimal.ZERO) -1 else 0
if (bmi > BigDecimal("18.5")) selectIndex++
if (bmi > BigDecimal("23.9")) selectIndex++
if (bmi > BigDecimal("27.9")) selectIndex++
val measurer = rememberTextMeasurer()
val standardArray = listOf("偏瘦 <=18.4", "正常 18.5 ~ 23.9", "超重 24.0 ~ 27.9", "肥胖 >= 28.0")
val colorArray = listOf(Color(0xff93b5cf), Color(0xff20a162), Color(0xfffcc515), Color(0xfff86b1d))
Canvas(
    modifier = Modifier.fillMaxSize()
) {
    val calcHeight = size.height - 30
    for (index in 0..3) {
        val offsetY = calcHeight / 4 * index + index * 10
        drawRect(
            color = colorArray[index],
            topLeft = Offset(0f, offsetY),
            size = Size(size.width, calcHeight / 4),
            style = Fill,
            alpha = if (index == selectIndex) 1f else .5f
        )
        drawRect(
            color = Color.DarkGray,
            topLeft = Offset(0f, offsetY),
            size = Size(size.width, calcHeight / 4),
            style = Stroke(if (index == selectIndex) 3f else 1f)
        )
        drawText(
            measurer,
            standardArray[index],
            style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.DarkGray),
            topLeft = Offset(5f, offsetY + 5),
        )
    }
}

然后输入数字,可以看到计算出19.3,是符合BMI正常水平的:

image.png

下面进行一些动画演示:

最终效果演示

输入不同的数值:

Animation2.gif

调节窗口大小:

Animation2.gif

还是挺好玩的,写起来也不怎么费事,可以动手来试一试!

本文于2023年7月20日写作并同时发布在lyrieek的稀土掘金社区与阿里云开发者社区。

目录
相关文章
|
7天前
|
敏捷开发 测试技术 持续交付
探索自动化测试在敏捷开发中的应用与挑战
本文深入探讨了自动化测试在现代软件开发流程,特别是敏捷开发环境中的重要作用和面临的挑战。通过分析自动化测试的基本原理、实施策略以及在实际项目中的应用案例,揭示了其在提高软件质量和加速产品交付方面的巨大潜力。同时,文章也指出了自动化测试实施过程中可能遇到的技术难题、成本考量及团队协作问题,并提出了相应的解决策略,为软件开发团队提供了有价值的参考和指导。
|
12天前
|
编解码 测试技术 开发工具
测试 iPhone 应用在不同屏幕尺寸和分辨率下的响应式效果
【10月更文挑战第23天】测试 iPhone 应用在不同屏幕尺寸和分辨率下的响应式效果是确保应用质量和用户体验的重要环节。通过手动测试、自动化测试、视觉效果评估、性能测试、用户体验测试等多种方法的综合运用,能够全面地发现应用在响应式效果方面存在的问题,并及时进行解决和优化。同时,持续的测试和优化也是不断提升应用质量和用户满意度的关键。
|
9天前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
22 2
|
9天前
|
Web App开发 定位技术 iOS开发
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
16 1
|
10天前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
24 2
|
21天前
|
监控 测试技术 持续交付
掌握跨平台测试策略:确保应用的无缝体验
【10月更文挑战第14天】在多元化设备和操作系统的今天,跨平台测试策略成为确保应用质量和性能的关键。本文探讨了跨平台测试的重要性、核心优势及实施步骤,涵盖Web、移动和桌面应用的测试方法,帮助开发者提高应用的无缝体验。
|
22天前
|
机器学习/深度学习 人工智能 自然语言处理
探索AI在软件测试中的创新应用与实践###
本文旨在探讨人工智能(AI)技术如何革新软件测试领域,提升测试效率、质量与覆盖范围。通过深入分析AI驱动的自动化测试工具、智能化缺陷预测模型及持续集成/持续部署(CI/CD)流程优化等关键方面,本研究揭示了AI技术在解决传统软件测试痛点中的潜力与价值。文章首先概述了软件测试的重要性和当前面临的挑战,随后详细介绍了AI技术在测试用例生成、执行、结果分析及维护中的应用实例,并展望了未来AI与软件测试深度融合的趋势,强调了技术伦理与质量控制的重要性。本文为软件开发与测试团队提供了关于如何有效利用AI技术提升测试效能的实践指南。 ###
|
24天前
|
机器学习/深度学习 弹性计算 自然语言处理
前端大模型应用笔记(二):最新llama3.2小参数版本1B的古董机测试 - 支持128K上下文,表现优异,和移动端更配
llama3.1支持128K上下文,6万字+输入,适用于多种场景。模型能力超出预期,但处理中文时需加中英翻译。测试显示,其英文支持较好,中文则需改进。llama3.2 1B参数量小,适合移动端和资源受限环境,可在阿里云2vCPU和4G ECS上运行。
|
7天前
|
NoSQL 测试技术 Go
自动化测试在 Go 开源库中的应用与实践
本文介绍了 Go 语言的自动化测试及其在 `go mongox` 库中的实践。Go 语言通过 `testing` 库和 `go test` 命令提供了简洁高效的测试框架,支持单元测试、集成测试和基准测试。`go mongox` 库通过单元测试和集成测试确保与 MongoDB 交互的正确性和稳定性,使用 Docker Compose 快速搭建测试环境。文章还探讨了表驱动测试、覆盖率检查和 Mock 工具的使用,强调了自动化测试在开源库中的重要性。
|
26天前
|
机器学习/深度学习 数据采集 人工智能
软件测试中的人工智能应用与挑战
【10月更文挑战第10天】 在当今信息技术飞速发展的时代,软件系统日益复杂且多样化,传统的手工测试方法已无法满足快速迭代和高效发布的需求。人工智能(AI)技术的引入为软件测试领域带来了新的希望和机遇。本文将探讨人工智能在软件测试中的应用现状、所面临的挑战以及未来的发展趋势,旨在启发读者思考如何更好地利用AI技术提升软件测试的效率和质量。
35 0
下一篇
无影云桌面