Вырезать изображение в Jetpack Compose

Я использую clip для обрезки изображения с помощью Compose, чтобы показать только левую часть изображения. Но он поддерживает ширину с пустым пространством. Как я могу обрезать правильную часть (отмечено красным)?

Я попытался установить пользовательскую ширину для изображения, но это не работает должным образом.

Вот код, который я использую,

object CropShape : Shape {
    override fun createOutline(
        size: androidx.compose.ui.geometry.Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline = Outline.Rectangle(
        Rect(Offset.Zero, androidx.compose.ui.geometry.Size((58 * density.density), size.height))
    )
}

@Composable
private fun test(){

Image(
        modifier = Modifier
            .height(142.dp)
            .clip(CropShape)
            .padding(start = 20.dp, bottom = 20.dp)
            .rotate(TiltedImageRotation)
        painter = resourcePainter(R.drawable.image),
        contentScale = ContentScale.FillHeight,
        contentDescription = null,
    )
}
1
0
180
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Установка ширины - правильный подход, вам просто нужно правильно alignment Я думаю - используйте alignment = Alignment.CenterStart, чтобы увидеть начало вашего изображения, а не центр, как на вашем втором изображении:

Image(
    modifier = Modifier
        .height(142.dp)
        .width(58.dp)
        .padding(start = 20.dp, bottom = 20.dp)
        .rotate(TiltedImageRotation)
    painter = resourcePainter(R.drawable.image),
    contentScale = ContentScale.FillHeight,
    alignment = Alignment.CenterStart,
    contentDescription = null,
)

Если вы хотите выровнять с некоторым смещением, как предложено в другом ответе, это можно сделать и с помощью Alignment и проще:

val density = LocalDensity.current
Image(
    alignment = Alignment { _, _, _ ->
        val xOffset = density.run { 58.dp.toPx() / 2 }.roundToInt()
        IntOffset(x = -xOffset, 0)
    }
)

Извините, я не заметил, что вы убрали необходимость в пользовательской форме в своем ответе. Это должен быть принятый ответ. Спасибо

Basim Sherif 10.05.2023 23:11

Отсечение не изменяет размеры составного объекта, а изменяет то, какая часть составного объекта рисуется. Без clip или clipToBounds вы можете рисовать что угодно из Composable с помощью модификаторов рисования, даже если размер Composable равен нулю.

Как в примере ниже с фигурой размером 200 пикселей.

 val shape = GenericShape { size: Size, layoutDirection: LayoutDirection ->
     addRect(Rect(0f, 0f, 200f, 200f))
 }

мы ограничиваем область рисования только до 200 пикселей, в то время как поле с изображением во фрагменте ниже покрывает 200.dp, но не 200 пикселей. 200.dp — это 200px * плотность устройства.

Row(modifier = Modifier.border(2.dp, Color.Blue)) {
     Box(
         modifier = Modifier
             .clip(shape)
             .clickable {

             }
             .size(100.dp)
             .background(Color.Green)
     ) {
         Image(
             modifier = Modifier.size(100.dp),
             painter = painterResource(id = R.drawable.landscape1),
             contentScale = ContentScale.FillBounds,
             contentDescription = null
         )
     }

     Box(
         modifier = Modifier
             .size(100.dp)
             .background(Color.Yellow)
     )
 }

Ограничение рисования размером при рисовании только части может быть достигнуто в любом случае, нет идеального или ключа к этому.

Вы можете сделать это, используя Modifier.layout{}, измерив полный размер Placeable, поместив его с размером для обрезки, например

modifier = Modifier
    .clipToBounds()
    .layout { measurable, constraints ->
        val width = (58 * density).toInt()

        val placeable = measurable.measure(
            constraints
        )

        layout(width, placeable.height) {
            placeable.place(0, 0)
        }
    }

И поскольку мы вырезаем область за пределы ширины макета, у нас есть только 58.dp компонуемых, которые рисуют из (0,0) в желаемую позицию.

Если вы измеряете составной объект шириной, указанной выше, вам также необходимо изменить выравнивание = Alignment.TopCenter, поскольку изображение использует Alignment.Center по умолчанию. Добавление выравнивания ко второму изображению решит проблему.

Image(
    modifier = Modifier.height(142.dp),
    painter = painterResource(R.drawable.landscape1),
    contentScale = ContentScale.FillHeight,
    contentDescription = null,
)

Row(
    modifier = Modifier.border(2.dp, Color.Green)
) {
    Image(
        modifier = Modifier
            .layout { measurable, constraints ->
                val width = (58 * density).toInt()

                val placeable = measurable.measure(
                    constraints.copy(
                        minWidth = width,
                        maxWidth = width
                    )
                )

                layout(placeable.width, placeable.height) {
                    placeable.place(0, 0)
                }
            }
            .height(142.dp),
        painter = painterResource(R.drawable.landscape1),
        contentScale = ContentScale.FillHeight,
        contentDescription = null,
    )
    Text(text = "Some Text After Image")
}

Row(
    modifier = Modifier.border(2.dp, Color.Blue)
) {
    Image(
        modifier = Modifier
            .clipToBounds()
            .layout { measurable, constraints ->
                val width = (58 * density).toInt()

                val placeable = measurable.measure(
                    constraints
                )

                layout(width, placeable.height) {
                    placeable.place(0, 0)
                }
            }
            .height(142.dp),
        painter = painterResource(R.drawable.landscape1),
        contentScale = ContentScale.FillHeight,
        contentDescription = null,
    )
    Text(text = "Some Text After Image")
}

Вы также можете использовать Canvas или Modifier.drawBehind для достижения того же результата. Следует учитывать, что мы обрезаем прямоугольник желаемого размера при рисовании внутренней ширины, которая является шириной фактического Painter или Bitmap, в то время как мы получаем высоту из Composable.

val painter = painterResource (R.drawable.landscape1)

Row(
    modifier = Modifier.border(2.dp, Color.Yellow)
) {
    Box(
        modifier = Modifier
            .width(58.dp)
            .drawBehind {
                clipRect(
                    left = 0f,
                    right = width
                ) {

                    with(painter) {
                        draw(size = Size(painter.intrinsicSize.width, size.height))
                    }
                }
            }
            .height(142.dp),
    )
    Text(text = "Some Text After Image")

Эй, да, я могу объяснить. На вопрос уже был дан ответ с отлично работающим кодом, но вы все равно хотели что-то добавить, так что давайте посмотрим на это. Что делает этот длинный модификатор layout{} в вашем первом блоке кода? Я думаю, это то же самое, что и .width(58.dp), не так ли? Тогда есть пример компенсации бонуса. Хорошо, я думаю, но опять же, вы можете сделать это намного проще с выравниванием: Alignment { IntOffset(x, y) }. Я не хочу показаться грубым, ваши ответы в основном хороши, и я часто их поддерживаю, но здесь вы просто добавили запутанный ненужный код к уже работающему ответу IMO.

Jan Bína 10.05.2023 18:37

Это не потому, что ты ответил после меня, конечно. Я уже объяснил причину. Вы говорите, что «это устраняет необходимость создания формы» - мой ответ тоже. Ключом к этому вопросу был Alignment, и он уже был предоставлен. Вы добавили длинный блок .layout {}, который никому не нужен, и бонус смещения, что приятно, но можно сделать намного проще, без .layout {}. Опять же, это не я «нетерпим к альтернативам». Ваша альтернатива просто делает что-то очень простое (alignment = CenterStart) довольно сложным, поэтому, на мой взгляд, это не очень хороший ответ.

Jan Bína 10.05.2023 18:59

Для достижения одной и той же цели может быть много способов, и добавление всего 9 строк кода не делает ничего сложного с Modifier.layout{}. Вы можете сделать это разными способами. Вы можете использовать макет или холст, клип с прямоугольником или любой другой вариант, который вы предпочитаете. При всем уважении, только потому, что вы использовали выравнивание, это не превращает его в ключ. Честно говоря, я бы предпочел увидеть ответ на макет, а не что-то более распространенное в качестве альтернативы, поскольку вы сталкиваетесь с решениями с макетом реже, чем с чем-либо еще. Могут быть сложные или простые альтернативы, и люди могут выбирать из них.

Thracian 10.05.2023 19:11

Да, кажется, мы не согласимся здесь. Замена чего-то такого цельного, как Modifier.weight, на 9-строчную .layout{} определенно не нужна и запутана. "только потому, что вы использовали выравнивание" - вы тоже использовали его! Вот что заставляет ваш ответ работать - опять же, вы просто добавили что-то ненужное поверх него.

Jan Bína 10.05.2023 19:54

Другие вопросы по теме