简体   繁体   English

如何使用 Modifier 镜像由 canvas 制作的可组合 function?

[英]How to mirror a composable function made by canvas with Modifier?

Problem description问题描述

I'm trying to create a component on android using Compose and Canvas that simulates a 7-segment display like this:我正在尝试使用 Compose 和 Canvas 在 android 上创建一个组件,它模拟这样的 7 段显示:

For that, I adopted a strategy of creating only half of this component and mirroring this part that I created downwards , so I would have the entire display.为此,我采用了只创建该组件的一半并向下镜像我创建的这部分的策略,这样我就有了整个显示。

This is the top part of the 7-segment display:这是 7 段显示的顶部:

But the problem is when "mirror" the top to bottom.但问题是什么时候从上到下“镜像”。 It turns out that when I add the Modifier.rotate(180f) the figure rotates around the origin of the canvas clockwise, and so it doesn't appear on the screen (it would if it were counterclockwise).事实证明,当我添加Modifier.rotate(180f)时,该图围绕 canvas 的原点顺时针旋转,因此它不会出现在屏幕上(如果它是逆时针旋转的话)。

I don't want to do this solution using a font for this, I would like to solve this problem through the canvas and compose itself.我不想为此使用字体来解决这个问题,我想通过 canvas 解决这个问题并自行组合。 If there is a smarter way to do this on canvas without necessarily needing a mirror I would like to know.如果有一种更聪明的方法可以在 canvas 上执行此操作而无需镜像,我想知道。

My code我的代码

Below is my code that I'm using to draw this:下面是我用来绘制它的代码:

DisplayComponent.kt显示组件.kt

@Composable
fun DisplayComponent(
    modifier: Modifier = Modifier,
    size: Int = 1000,
    color: Color = MaterialTheme.colors.primary,
) {
    Column(modifier = modifier) {
        HalfDisplayComponent(size, color)
        HalfDisplayComponent(
            modifier = Modifier.rotate(180f),
            size = size,
            color = color
        )
    }
}

@Composable
private fun HalfDisplayComponent(
    size: Int,
    color: Color,
    modifier: Modifier = Modifier,
) {
    Box(modifier = modifier) {
        LedModel.values().forEach {
            LedComponent(
                ledModel = it,
                size = size,
                color = color
            )
        }
    }
}

LedModel.kt LedModel.kt

enum class LedModel(val coordinates: List<Pair<Float, Float>>) {
    HorizontalTop(
        listOf(
            Pair(0.04f, 0.03f), // Point A
            Pair(0.07f, 0f), // Point B
            Pair(0.37f, 0f), // Point C
            Pair(0.4f, 0.03f), // Point D
            Pair(0.34f, 0.08f), // Point E
            Pair(0.1f, 0.08f), // Point F
        )
    ),
    VerticalRight(
        listOf(
            Pair(0.41f, 0.04f), // Point A
            Pair(0.44f, 0.07f), // Point B
            Pair(0.44f, 0.37f), // Point C
            Pair(0.41f, 0.4f), // Point D
            Pair(0.35f, 0.35f), // Point E
            Pair(0.35f, 0.09f), // Point F
        )
    ),
    VerticalLeft(
        listOf(
            Pair(0.03f, 0.4f), // Point A
            Pair(0f, 0.37f), // Point B
            Pair(0f, 0.07f), // Point C
            Pair(0.03f, 0.04f), // Point D
            Pair(0.09f, 0.09f), // Point E
            Pair(0.09f, 0.35f), // Point F
        )
    ),
    HorizontalBottom(
        listOf(
            Pair(0.1f, 0.36f), // Point A
            Pair(0.34f, 0.36f), // Point B
            Pair(0.39f, 0.4f), // Point C
            Pair(0.05f, 0.4f), // Point D
        )
    ),
}

LedComponent.kt LedComponent.kt

@Composable
fun LedComponent(
    modifier: Modifier = Modifier,
    size: Int = 30,
    color: Color = MaterialTheme.colors.primary,
    ledModel: LedModel = LedModel.HorizontalTop
) = getPath(ledModel.coordinates).let { path ->
    Canvas(modifier = modifier.scale(size.toFloat())) {
        drawPath(path, color)
    }
}

private fun getPath(coordinates: List<Pair<Float, Float>>): Path = Path().apply {
    coordinates.map {
        transformPointCoordinate(it)
    }.forEachIndexed { index, point ->
        if (index == 0) moveTo(point.x, point.y) else lineTo(point.x, point.y)
    }
}

private fun transformPointCoordinate(point: Pair<Float, Float>) =
    Offset(point.first.dp.value, point.second.dp.value)

My failed attempt我失败的尝试

As described earlier, I tried adding a Modifier by rotating the composable of the display but it didn't work .如前所述,我尝试通过旋转显示的可组合项来添加Modifier器,但没有成功 I did it this way:我是这样做的:

@Composable
fun DisplayComponent(
    modifier: Modifier = Modifier,
    size: Int = 1000,
    color: Color = MaterialTheme.colors.primary,
) {
    Column(modifier = modifier) {
        DisplayFABGComponent(size, color)
        DisplayFABGComponent(
            modifier = Modifier.rotate(180f),
            size = size,
            color = color
        )
    }
}

There are many things wrong with the code you posted above.您上面发布的代码有很多问题。

First of all in Jetpack Compose even if your Canvas has 0.dp size you can still draw anywhere which is the first issue in your question.首先,在 Jetpack Compose 中,即使您的 Canvas 大小为 0.dp,您仍然可以在任何地方绘制,这是您问题中的第一个问题。 Your Canvas has no size modifier, which you can verify by printing DrawScope.size as below.您的 Canvas 没有大小修饰符,您可以通过如下打印DrawScope.size来验证。

fun LedComponent(
    modifier: Modifier = Modifier,
    size: Int = 1000,
    color: Color = MaterialTheme.colorScheme.primary,
    ledModel: LedModel = LedModel.HorizontalTop
) = getPath(ledModel.coordinates).let { path ->
    Canvas(
        modifier = modifier.scale(size.toFloat())
    ) {

        println("CANVAS size: ${this.size}")
        drawPath(path, color)
    }
}

any value you enter makes no difference other than Modifier.scale(0f) , also this is not how you should build or scale your drawing either.除了Modifier.scale(0f)之外,您输入的任何值都没有区别,这也不是您构建或缩放绘图的方式。

If you set size for your Canvas such as如果您为 Canvas 设置大小,例如

@Composable
fun DisplayComponent(
    modifier: Modifier = Modifier,
    size: Int = 1000,
    color: Color = MaterialTheme.colorScheme.primary,
) {
    Column(modifier = modifier) {
        HalfDisplayComponent(
            size,
            color,
            Modifier
                .size(200.dp)
                .border(2.dp,Color.Red)
        )
        HalfDisplayComponent(
            modifier = Modifier
                .size(200.dp)
                .border(2.dp, Color.Cyan)
                .rotate(180f),
            size = size,
            color = color
        )
    }
}

Rotation works but what you draw is not symmetric as in image in your question.旋转有效,但您绘制的内容并不像您问题中的图像那样对称。

在此处输入图像描述

point.first.dp.value this snippet does nothing. point.first.dp.value这个片段什么都不做。 What it does is adds dp to float then gets float.它所做的是将 dp 添加到浮动然后得到浮动。 This is not how you do float/dp conversions and which is not necessary either.这不是您进行 float/dp 转换的方式,也不是必需的。

You can achieve your goal with one Canvas or using Modifier.drawBehind{}.您可以使用一个 Canvas或使用 Modifier.drawBehind{} 来实现您的目标。 Create a Path using Size as reference for half component then draw again and rotate it or create a path that contains full led component.使用大小作为一半组件的参考创建路径,然后再次绘制并旋转它或创建包含完整 LED 组件的路径。 Or you can have paths for each sections if you wish show LED digits separately.或者,如果您希望单独显示 LED 数字,则可以为每个部分设置路径。

This is a simple example to build only one diamond shape, then translate and rotate it to build hourglass like shape using half component.这是一个简单的例子,只构建一个菱形,然后平移和旋转它以使用半组件构建沙漏形状。 You can use this sample as demonstration for how to create Path using Size as reference, translate and rotate.您可以使用此示例演示如何使用大小作为参考、平移和旋转来创建路径。

fun getHalfPath(path: Path, size: Size) {
    path.apply {
        val width = size.width
        val height = size.height / 2
        moveTo(width * 0f, height * .5f)
        lineTo(width * .3f, height * 0.3f)
        lineTo(width * .7f, height * 0.3f)
        lineTo(width * 1f, height * .5f)
        lineTo(width * .5f, height * 1f)
        lineTo(width * 0f, height * .5f)
    }
}

You need to use aspect ratio of 1/2f to be able to have symmetric drawing.您需要使用 1/2f 的纵横比才能进行对称绘图。 Green border is to show bounds of Box composable.绿色边框用于显示 Box 可组合项的边界。

val path = remember {
    Path()
}

Box(modifier = Modifier
    .border(3.dp, Color.Green)
    .fillMaxWidth(.4f)
    .aspectRatio(1 / 2f)
    .drawBehind {
        if (path.isEmpty) {
            getHalfPath(path, size)
        }

        drawPath(
            path = path,
            color = Color.Red,
            style = Stroke(2.dp.toPx())
        )

        withTransform(
            {
                translate(0f, size.height / 2f)
                rotate(
                    degrees = 180f,
                    pivot = Offset(center.x, center.y / 2)
                )
            }
        ) {
            drawPath(
                path = path,
                color = Color.Black,
                style = Stroke(2.dp.toPx())
            )
        }
    }

Result结果

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM