简体   繁体   中英

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:

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:

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).

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. If there is a smarter way to do this on canvas without necessarily needing a mirror I would like to know.

My code

Below is my code that I'm using to draw this:

DisplayComponent.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

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

@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 . 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. Your Canvas has no size modifier, which you can verify by printing DrawScope.size as below.

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.

If you set size for your Canvas such as

@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. What it does is adds dp to float then gets float. This is not how you do float/dp conversions and which is not necessary either.

You can achieve your goal with one Canvas or using 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. Or you can have paths for each sections if you wish show LED digits separately.

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. Green border is to show bounds of Box composable.

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

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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