Как отразить компонуемую функцию, созданную холстом, с помощью модификатора?
Описание проблемы
Я пытаюсь создать компонент на Android с помощью Compose и Canvas, который имитирует 7-сегментный дисплей следующим образом:
![](/images/8f2116713ab52387aee79aa4743d19f4255deffe.png)
Для этого я принял стратегию создания только половины этого компонента и зеркального отражения этой части, которую я создал, вниз , чтобы у меня был весь дисплей.
Это верхняя часть 7-сегментного дисплея:
![](/images/62764d6f711392de81b04565601ab9676f5ec2ec.png)
Но проблема в том, когда "отзеркалить" сверху вниз. Оказывается, когда я добавляюModifier.rotate(180f)
фигура вращается вокруг исходной точки холста по часовой стрелке и поэтому не появляется на экране (была бы, если бы вращалась против часовой стрелки).
Я не хочу делать это решение, используя для этого шрифт, я хотел бы решить эту проблему через холст и составить сам. Если есть более умный способ сделать это на холсте без необходимости зеркала, я хотел бы знать.
Мой код
Ниже приведен мой код, который я использую, чтобы нарисовать это:
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)
Моя неудачная попытка
Как описано ранее, я попытался добавитьModifier
путем поворота компоновки дисплея, но это не сработало . Я сделал это следующим образом:
@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
)
}
}
1 ответ
В коде, который вы разместили выше, много ошибок.
Прежде всего, в Jetpack Compose, даже если ваш Canvas имеет размер 0.dp, вы все равно можете рисовать где угодно, что является первой проблемой в вашем вопросе. У вашего холста нет модификатора размера, в чем вы можете убедиться, напечатав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)
}
}
любое введенное вами значение не имеет значения, кромеModifier.scale(0f)
, также это не то, как вы должны строить или масштабировать свой рисунок.
Если вы установите размер для своего холста, например
@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
)
}
}
Вращение работает, но то, что вы рисуете, не симметрично, как на изображении в вашем вопросе.
point.first.dp.value
этот фрагмент ничего не делает. Что он делает, так это добавляет dp к float, а затем получает float. Это не то, как вы выполняете преобразования float/dp, и в этом нет необходимости.
Вы можете достичь своей цели с помощью одного холста или с помощью Modifier.drawBehind{}. Создайте путь, используя размер в качестве эталона для половины компонента, затем снова нарисуйте и поверните его или создайте путь, содержащий полный светодиодный компонент. Или вы можете иметь пути для каждой секции, если вы хотите показывать светодиодные цифры отдельно.
Это простой пример создания только одной ромбовидной формы, а затем ее перемещения и вращения для построения формы, похожей на песочные часы, с использованием половинного компонента. Вы можете использовать этот пример в качестве демонстрации того, как создать путь, используя размер в качестве эталона, перевести и повернуть.
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)
}
}
Вам нужно использовать соотношение сторон 1/2f, чтобы иметь возможность иметь симметричный рисунок. Зеленая рамка показывает границы компонуемого 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())
)
}
}
Результат