组合:创建带圆形背景的文本

21

我来自SwiftUI,想要创建一个Text视图,其中背景是一个圆形,当Text中的文本变长时,圆形的宽度/高度也随之增加。

由于在Compose中没有像SwiftUI中的Circle()那样的方法,因此我只创建了一个Spacer并进行了裁剪。以下代码使用ConstraintLayout,因为我不知道如何获取Text的宽度,以便设置我的Circle组件的大小与其相同:

@Composable
fun CircleDemo() {
    ConstraintLayout {
        val (circle, text) = createRefs()

        Spacer(
            modifier = Modifier
                .background(Color.Black)
                .constrainAs(circle) {
                    centerTo(text)
                }
        )

        Text(
            text = "Hello",
            color = Color.White,
            modifier = Modifier
                .constrainAs(text) {
                    centerTo(parent)
                }
        )
    }
}


我可以使用 mutableStateOf { 0 },并将其更新在附加到 TextonGloballyPositioned 修饰符中,然后将其设置为 SpacerrequiredSize,但是问题是:一,这似乎很愚蠢;二,Spacer 现在绘制在了 ConstraintLayout 的边界之外。

在视觉上,我想实现这个效果:

A black circle with the word Hello entered inside

请问我应该如何做到这一点?谢谢 :)

6个回答

34

你也可以在textView的修饰符中使用drawBehind,如下所示:

Text(
     modifier = Modifier
         .padding(16.dp)
         .drawBehind {
               drawCircle(
                    color = red,
                    radius = this.size.maxDimension
               )
          },
     text = "Hello",
)

当然,您可以根据自己的需求进一步自定义半径和其他属性!

在此输入图像描述


3
这里的圆圈将是文本大小的两倍,并且将溢出 Composable。文本最大正方形大小被给定为圆圈半径(而不是圆圈直径)。如果您希望圆圈恰好适合文本大小,则设置:radius = this.size.maxDimension / 2.0f。然后,您可以在圆圈周围添加内部和外部填充。 - Jojo56400

26

您需要根据文本的尺寸计算背景圆的尺寸。
您可以使用基于Modifier.layout的自定义修饰符:

fun Modifier.circleLayout() =
    layout { measurable, constraints ->
        // Measure the composable
        val placeable = measurable.measure(constraints)

        //get the current max dimension to assign width=height
        val currentHeight = placeable.height
        val currentWidth = placeable.width
        val newDiameter = maxOf(currentHeight, currentWidth)

        //assign the dimension and the center position
        layout(newDiameter, newDiameter) {
            // Where the composable gets placed
            placeable.placeRelative((newDiameter-currentWidth)/2, (newDiameter-currentHeight)/2)
        }
    }

然后只需将带有背景的 CircleShape 应用于 Text

    Text(
        text = "Hello World",
        textAlign = TextAlign.Center,
        color = Color.White,
        modifier = Modifier
            .background(Color.Black, shape = CircleShape)
            .circleLayout()
            .padding(8.dp)
    )

在此输入图片描述


2
谢谢,对于像“Hello World”这样较长的字符串很有效,但我不确定使其对“1”起作用需要什么。 - Benoit
2
@Benoit 只需为“Text”元素分配一个minSize。例如 .defaultMinSize(24.dp) - Gabriele Mariotti
1
@GabrieleMariotti 对于小字符串,即使设置了minSize,也无法很好地工作。 - Abdul Mateen

6
@Composable
fun Avatar(color: Color) {
    Box(
        modifier = Modifier
            .size(size.Dp)
            .clip(CircleShape)
            .background(color = color),
        contentAlignment = Alignment.Center
    ) {
        Text(text = "Hello World")
    }
}

嗨,size.Dp是什么?你的代码中没有定义size。 - Marty Miller
如果圆形是重要的对象,并且文本适应它,那么这就是完美的选择。对于我的使用情况来说,这是正确的选择。接受的答案根据文本大小计算圆形,所以所有的圆形都会不同。如果在列表中展示它们,这个结果可能会成为一个问题。 - UHDante
1
嘿 @MartyMiller,size.Dp 可以是 16.dp 例如。 - undefined

1
使用一个黑色圆形的背景可绘制对象,放在透明颜色中。背景可绘制对象将会被拉伸以填充整个视图,圆形应该能够很好地被拉伸而不会出现伪影。

你应该将这个作为注释而不是答案添加。 - Farid

1
扩展@GabrieleMariotti的回答,您可以将这三个修饰符合并为一个,以便更容易使用。
/**
 * Draws circle with a solid [color] behind the content.
 *
 * @param color The color of the circle.
 * @param padding The padding to be applied externally to the circular shape. It determines the spacing between
 * the edge of the circle and the content inside.
 *
 * @return Combined [Modifier] that first draws the background circle and then centers the layout.
 */
fun Modifier.circleBackground(color: Color, padding: Dp): Modifier {
    val backgroundModifier = drawBehind {
        drawCircle(color, size.width / 2f, center = Offset(size.width / 2f, size.height / 2f))
    }

    val layoutModifier = layout { measurable, constraints ->
        // Adjust the constraints by the padding amount
        val adjustedConstraints = constraints.offset(-padding.roundToPx())

        // Measure the composable with the adjusted constraints
        val placeable = measurable.measure(adjustedConstraints)

        // Get the current max dimension to assign width=height
        val currentHeight = placeable.height
        val currentWidth = placeable.width
        val newDiameter = maxOf(currentHeight, currentWidth) + padding.roundToPx() * 2

        // Assign the dimension and the center position
        layout(newDiameter, newDiameter) {
            // Place the composable at the calculated position
            placeable.placeRelative((newDiameter - currentWidth) / 2, (newDiameter - currentHeight) / 2)
        }
    }

    return this then backgroundModifier then layoutModifier
}

像这样使用:

Text(
    text = "Hello World",
    color = Color.White,
    modifier = Modifier
        .circleBackground(color = Color.DarkGray, padding = 6.dp)
)

Text with circular background


0

答案标记为正确的有一点错误。那是因为它计算了圆的半径...实际上这取决于许多因素。你必须考虑以下几点:

  1. 文本的父级是谁?
  2. 父级有什么修改器?
  3. 你的文本有什么修改器?

具有易于自定义的圆形的正确答案可以是:

@Composable
fun CircleDemo() {
    // Initialize width as it is not exist
    val textWidthState: MutableState<Dp?> = remember { mutableStateOf(null) }
    val modifierWithCalculatedSize: State<Modifier> = 
    // You must provide new Modifier whenever width of Text is changed                                                                
        remember(textWidthState.value) {
            // Modifier for parent which draw the Circle
            val mod = Modifier
                .padding(horizontal = 16.dp)
                .padding(bottom = 16.dp)
            // Provide new Modifier only when calculation produces new value
            derivedStateOf {
                val currentWidth = textWidthState.value
                if (currentWidth != null) mod.size(currentWidth) else mod
            }
        }
    // Do not use Modifier with size(especially width) for Box. 
    Box(
        modifier = modifierWithCalculatedSize.value
             .clip(CircleShape),
         // Center your text inside Circle
         contentAlignment = Alignment.Center
     ) {
         val density = LocalDensity.current
         Text(
             text = "Hello",
             color = Color.White,
             modifier = Modifier
                 // Obtain width of Text after position
                 .onGloballyPositioned {
                     textWidthState.value =  with(density) { 
                         it.size.width.toDp() 
                     }
                 }
                 // Adjust Circle size
                 .padding(8.dp)
        )
    }
}

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