@@ -34,6 +34,7 @@ import androidx.compose.ui.Alignment
3434import androidx.compose.ui.Modifier
3535import androidx.compose.ui.draw.clip
3636import androidx.compose.ui.graphics.Color
37+ import androidx.compose.ui.layout.Layout
3738import androidx.compose.ui.text.font.FontWeight
3839import androidx.compose.ui.unit.dp
3940import androidx.compose.ui.unit.sp
@@ -45,6 +46,7 @@ import com.clhs.score.data.scoreDistributions
4546import com.clhs.score.data.shortenSubjectName
4647import kotlinx.coroutines.delay
4748import kotlin.math.floor
49+ import kotlin.math.roundToInt
4850
4951private val SubjectPositive = Color (0xFF0B7D3B )
5052private val SubjectWarning = Color (0xFFE87500 )
@@ -304,9 +306,15 @@ private fun DistributionRow(item: com.clhs.score.data.ScoreDistribution, total:
304306
305307@Composable
306308internal fun StandardPositionBar (score : Double , standard : GradeStandard ) {
307- val marks = listOfNotNull(standard.bottom, standard.back, standard.average, standard.front, standard.top)
308- val low = marks.minOrNull() ? : 0.0
309- val high = marks.maxOrNull()?.coerceAtLeast(low + 1.0 ) ? : 100.0
309+ val marks = listOfNotNull(
310+ standard.bottom?.let { StandardMarkSpec (" 底" , it) },
311+ standard.back?.let { StandardMarkSpec (" 後" , it) },
312+ standard.average?.let { StandardMarkSpec (" 均" , it) },
313+ standard.front?.let { StandardMarkSpec (" 前" , it) },
314+ standard.top?.let { StandardMarkSpec (" 頂" , it) },
315+ )
316+ val low = marks.minOfOrNull { it.value } ? : 0.0
317+ val high = marks.maxOfOrNull { it.value }?.coerceAtLeast(low + 1.0 ) ? : 100.0
310318 val fraction = ((score - low) / (high - low)).coerceIn(0.0 , 1.0 ).toFloat()
311319 Column (verticalArrangement = Arrangement .spacedBy(8 .dp)) {
312320 Text (" 班級五標" , style = MaterialTheme .typography.labelLarge, color = MaterialTheme .colorScheme.onSurfaceVariant)
@@ -330,12 +338,40 @@ internal fun StandardPositionBar(score: Double, standard: GradeStandard) {
330338 .background(MaterialTheme .colorScheme.outlineVariant, RoundedCornerShape (999 .dp)),
331339 )
332340 }
333- Row (horizontalArrangement = Arrangement .spacedBy(8 .dp), modifier = Modifier .fillMaxWidth()) {
334- StandardMark (modifier = Modifier .weight(1f ), label = " 底" , value = standard.bottom)
335- StandardMark (modifier = Modifier .weight(1f ), label = " 後" , value = standard.back)
336- StandardMark (modifier = Modifier .weight(1f ), label = " 均" , value = standard.average)
337- StandardMark (modifier = Modifier .weight(1f ), label = " 前" , value = standard.front)
338- StandardMark (modifier = Modifier .weight(1f ), label = " 頂" , value = standard.top)
341+ ProportionalStandardMarks (marks = marks, low = low, high = high)
342+ }
343+ }
344+
345+ private data class StandardMarkSpec (
346+ val label : String ,
347+ val value : Double ,
348+ )
349+
350+ @Composable
351+ private fun ProportionalStandardMarks (marks : List <StandardMarkSpec >, low : Double , high : Double ) {
352+ val range = (high - low).takeIf { it > 0.0 } ? : 1.0
353+ Layout (
354+ modifier = Modifier .fillMaxWidth(),
355+ content = {
356+ marks.forEach { mark ->
357+ StandardMark (modifier = Modifier , label = mark.label, value = mark.value)
358+ }
359+ },
360+ ) { measurables, constraints ->
361+ val placeables = measurables.map { measurable ->
362+ measurable.measure(constraints.copy(minWidth = 0 ))
363+ }
364+ val width = constraints.maxWidth
365+ val height = placeables.maxOfOrNull { it.height } ? : 0
366+ layout(width, height) {
367+ placeables.forEachIndexed { index, placeable ->
368+ val mark = marks[index]
369+ val fraction = ((mark.value - low) / range).coerceIn(0.0 , 1.0 )
370+ val x = (fraction * width - placeable.width / 2.0 )
371+ .roundToInt()
372+ .coerceIn(0 , (width - placeable.width).coerceAtLeast(0 ))
373+ placeable.placeRelative(x, 0 )
374+ }
339375 }
340376 }
341377}
0 commit comments