Are you at a healthy weight? Make a BMI application test with Jetpack!

In the morning, I found some problems with a BMI display panel made a few years ago. I thought that Jetpack Compose Desktop can do this well. Although it is impossible to use this to replace the system on the webpage, the effect is definitely good, and it is worth trying.

Jetpack Compose DesktopI won’t talk about the dependency part. You can refer to the previous article to use the minimal configuration of Jetpack Compose Desktop to make a Windows desktop time display and use Jetpack Compose Desktop to make a small game of Sokoban . It is still the default dependency, which is actually one compose.desktop.currentOs.

basic layout

Then firstly revise the basic window and make a vertical rectangle:

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

Then app()draw the main part in it, that is, a centered one Box, which Colunmis used to arrange two TextFieldto accept height and weight respectively:

@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("体重") })
            }
        }
    }
}

Then you can see this effect:

image.png

record input

Then it is necessary to save the data of height and weight and do verification, so first define two state variables:

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

Here is a general method 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) })
}

Then replace the two just now TextFieldwith generateInput()regenerated text boxes:

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

In this way, the data can be saved and can be verified as a number.

Calculating BMI

Here we need to do a special method calculation, the method name can be called 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)
}

Here you can redefine weightand the grammatical feature name shadowed ( Name shadowed ), you can understand that the subsequent calculations look very refreshing and also benefit heightfrom the direct operation provided for . Then make another one below for display:kotlinkotlinBigDecimal/ColumnText()

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

This text is actually the core of the entire window, so I deliberately increased the size a little bit, and then I can see the effect:

image.png

But this alone is definitely not enough. You can’t let people memorize the BMI standard or find a table for comparison every time you forget it, so let’s make a picture below to provide a reference.

Standard value reference image production

Next, we will put one Canvasto display the BMI index and a column chart. We all know that BMI is divided into 4 levels (thin, normal, overweight, and obese), so let's draw the label first. Then draw a frame to enclose it. The 4 levels are 4 frames. There should be some blank space between the frames. There should be 3 blank spaces in the 4 frames. Each blank should be positioned as follows 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)
        )
    }
}

Then you can see a relatively simple effect:

image.png

Then you need to fill each area with a different color. For example, health is green, and obesity should be orange. But the frame line still needs to be preserved, but drawRectthere is no way to add it at the same Filltime Stroke, so only two can be drawn rect, so the above code can be modified as follows:

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),
        )
    }
}

Why did I remove it here drawText? In fact, it is because it is covered Fillby rect. If rectyou draw the characters after drawing, it will be no problem, so you have to drawTextmove them down and set the color of the characters a little bit. The effect is like this:

image.pngThe next thing to do is to make rectthe default transparent. If the above BMI value belongs to its range, then the color will be darkened and the border will be thickened. This will be linked with the above. The assignment of the middle row should also be taken out for later judgments 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),
        )
    }
}

Then enter the number, you can see that the calculation 19.3is in line with the normal level of BMI:

image.png

Here are some animations:

Demonstration of the final effect

Enter a different value:

Animation2.gif

Adjust window size:

Animation2.gif

It's still quite fun, and it doesn't take too much trouble to write, so you can try it yourself!

This article was written on July 20, 2023 and published simultaneously in lyrieek's rare earth nuggets community and Alibaba Cloud developer community.

Guess you like

Origin juejin.im/post/7257774805432664121