如何在Jetpack Compose中仅添加底部边框

40

我想在布局的底部添加边框。 我知道可以使用Divider组合项,但我只想学习如何绘制边框

目前,我可以为所有边添加边框,这不是我想要的。

Row(
    modifier = Modifier
        .border(border = BorderStroke(width = 1.dp, Color.LightGray))
) {
    TextField(value = "", onValueChange = {}, modifier = Modifier.weight(1f))
    Switch(checked = true, onCheckedChange = {})
    Icon(Icons.Filled.Close, "Remove", tint = Color.Gray)
}

据我所知,没有实现它的功能。您是否考虑为行范围添加另一个子元素作为边框? - AgentP
6
这真让人惊讶,居然没有一个直截了当的解决方案!在我看来,使用隔板似乎是最好的解决方案! - Raw Hasan
8个回答

41
您可以使用drawBehind修饰符绘制一条线。
例如:
Row(
    modifier = Modifier
        .drawBehind {

            val strokeWidth = indicatorWidth.value * density
            val y = size.height - strokeWidth / 2

            drawLine(
                Color.LightGray,
                Offset(0f, y),
                Offset(size.width, y),
                strokeWidth
            )
        }){
    //....
}

输入图像描述

如果您愿意,您可以使用与上述代码相同的代码构建自定义修改器

fun Modifier.bottomBorder(strokeWidth: Dp, color: Color) = composed(
    factory = {
        val density = LocalDensity.current    
        val strokeWidthPx = density.run { strokeWidth.toPx() }

        Modifier.drawBehind {
            val width = size.width
            val height = size.height - strokeWidthPx/2

            drawLine(
                color = color,
                start = Offset(x = 0f, y = height),
                end = Offset(x = width , y = height),
                strokeWidth = strokeWidthPx
            )
        }
    }
)

然后只需应用它:

Row(
    modifier = Modifier
        .padding(horizontal = 8.dp)
        .fillMaxWidth()
        .bottomBorder(1.dp, DarkGray)
){
    //Row content
}

3
我不喜欢这个解决方案,但它相当简单且有效。;-) - Greg Harley

5
使用“分隔符”对我很有效。
Column {
        Divider (
            color = Color.White,
            modifier = Modifier
                .height(1.dp)  
                .fillMaxWidth()
        )
        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
        ) {
// Something else
        }
    }

enter image description here


虽然这并不是对所问问题的回答,但这仍然是一个巧妙的解决方案,而且在许多单边边框的使用情况下也是语义上正确的。 - undefined

4
你可以使用底边线的粗细作为参数,在元素底部定义一个矩形形状:
private fun getBottomLineShape(bottomLineThickness: Float) : Shape {
    return GenericShape { size, _ ->
        // 1) Bottom-left corner
        moveTo(0f, size.height)
        // 2) Bottom-right corner
        lineTo(size.width, size.height)
        // 3) Top-right corner
        lineTo(size.width, size.height - bottomLineThickness)
        // 4) Top-left corner
        lineTo(0f, size.height - bottomLineThickness)
    }
}

然后像这样在边框修饰器中使用它:

val lineThickness = with(LocalDensity.current) {[desired_thickness_in_dp].toPx()}
Row(
    modifier = Modifier
       .height(rowHeight)
       .border(width = lineThickness,
               color = Color.Black,
               shape = getBottomLineShape(lineThickness))
) {
  // Stuff in the row
}

这种方法很棒!它(a)完全符合要求,(b)也正是.borderShape的初衷。然而,理想情况下,我认为bottomLineThickness应该是以Dp为单位,并且在Shape创建过程中应该将其转换为Float px。从我所看到的情况来看,GenericShape不会遵循传递给其createOutlineDensity,所以我认为在这里创建一个自定义的Shape子类型是值得一试的。 - undefined
1
或者作为一个简单但有点巧妙的解决方案,可以将getBottomLineShape注释为@Composable,并在其中使用LocalDensity.current将Dp转换。 - undefined

3

好的,这应该能解决问题:

@Suppress("UnnecessaryComposedModifier")
fun Modifier.topRectBorder(width: Dp = Dp.Hairline, brush: Brush = SolidColor(Color.Black)): Modifier = composed(
    factory = {
        this.then(
            Modifier.drawWithCache {
                onDrawWithContent {
                    drawContent()
                    drawLine(brush, Offset(width.value, 0f), Offset(size.width - width.value, 0f))
                }
            }
        )
    },
    inspectorInfo = debugInspectorInfo {
        name = "border"
        properties["width"] = width
        if (brush is SolidColor) {
            properties["color"] = brush.value
            value = brush.value
        } else {
            properties["brush"] = brush
        }
        properties["shape"] = RectangleShape
    }
)


这是原始边框修改器的实现方式。 - Richard Onslow Roper

3
你可以在绘图范围内画一条线。我认为,在代码中使用分隔符看起来更加清晰。
Row(modifier = Modifier
  .drawWithContent {
    drawContent()
    clipRect { // Not needed if you do not care about painting half stroke outside
      val strokeWidth = Stroke.DefaultMiter
      val y = size.height // - strokeWidth 
          // if the whole line should be inside component
      drawLine(
        brush = SolidColor(Color.Red),
        strokeWidth = strokeWidth,
        cap = StrokeCap.Square,
        start = Offset.Zero.copy(y = y),
        end = Offset(x = size.width, y = y)
      )
    }
  }
) {
  Text("test")
}

1

你可以轻松地使用链接修饰符来完成。这甚至让你可以根据需要在每一侧使用不同大小的线条和不同形状的边框。

你在寻找什么

Box(modifier = Modifier.background(MaterialTheme.colorScheme.outline)
                       .padding(bottom = 1.dp)
                       .background(MaterialTheme.colorScheme.background
) {...}

更加复杂

Box(modifier = Modifier.background(MaterialTheme.colorScheme.outline, shape = RoundedCornerShape(8.dp))
                       .padding(horizontal = 1.dp, vertical = 2.dp)
                       .background(MaterialTheme.colorScheme.background, shape = RoundedCornerShape(8.dp))
) {...}

1
您可以在 Composable 视图底部轻松地添加此分隔符:

Divider(
        color = Color.LightGray,
        modifier = Modifier
            .height(0.5.dp)
            .fillMaxHeight()
            .fillMaxWidth()
    )

1
听起来像是一个正确的实施方式,谢谢! - undefined

0
这样怎么样:
@Preview(showBackground = true)
@Composable private fun BottomBorder () {
    Text(
        text = "Hello, world!",
        modifier = Modifier
            .padding(20.dp) // some space around the Text component
            .drawWithContent {
                drawContent()
                drawLine(
                    color = Color.Red,
                    start = Offset(0f, size.height),
                    end = Offset(size.width, size.height),
                    strokeWidth = 2f
                )
            }
    )
}


截图:

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接