在Jetpack Compose中剪裁图像

huangapple go评论69阅读模式
英文:

Clip image in Jetpack Compose

问题

I am using clip to trim an Image using Compose, to show only the left edge part of the image. But it maintains the width with blank space. How can I crop the right part(marked in red)?

我正在使用Compose中的clip来修剪图像,只显示图像的左侧部分。但是它保持了宽度并留有空白空间。我该如何裁剪右侧部分(红色标记)?

I tried setting a custom width for Image but its not working as expected.

我尝试为图像设置自定义宽度,但它的效果不如预期。

Here is the code I am using,

这是我正在使用的代码:

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,
    )
}

这是我正在使用的代码:

英文:

I am using clip to trim an Image using Compose, to show only the left edge part of the image. But it maintains the width with blank space. How can I crop the right part(marked in red)?

在Jetpack Compose中剪裁图像

I tried setting a custom width for Image but its not working as expected.

在Jetpack Compose中剪裁图像

Here is the code I am using,

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

得分: 4

设置宽度是正确的方法,我认为你只需要正确的 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)
    }
)
英文:

Setting the width is the right approach, you just need proper alignment I think - use alignment = Alignment.CenterStart to see the start of your image and not the center like on your second picture:

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

If you want to align with some offset, as suggested in the other answer, that can be done with Alignment too, and more easily:

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

答案2

得分: 2

Clipping 不会改变 Composable 的尺寸,它只会改变 Composable 中被绘制的部分。如果没有 clip 或 clipToBounds,即使 Composable 的尺寸为零,你仍然可以通过绘制修饰符在 Composable 之外绘制任何内容。

在下面的示例中,使用了一个大小为 200px 的形状:

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

我们限制了绘制区域只有 200px,而下面的示例中的 Box 包含了一个 Image,其大小为 200.dp,但不是 200px。200.dp 等于 200px 乘以设备的密度。

在Jetpack Compose中剪裁图像

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 = Alignment.TopCenter,因为图像默认使用 Alignment.Center。在第二个 Image 中添加 alignment 将解决此问题。

在Jetpack Compose中剪裁图像

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 来实现相同的结果。要考虑的事情是,在绘制内在宽度时,我们要将裁剪区域限制在所需大小,而高度则来自 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")
}
英文:

Clipping does not change dimensions of a Composable, it changes which section of a Composable is drawn. Without clip or clipToBounds you can draw anything out of a Composable via draw modifiers even if size of a Composable is zero.

As in example below with a shape with 200px

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

we limit drawing area only to 200px while Box with Image in snippet below covers 200.dp, but not 200px. 200.dp is 200px * density of device.

在Jetpack Compose中剪裁图像

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

Limiting drawing to size while drawing only portion can be achieved in any way there is no perfect or key to this.

You can do it by using Modifier.layout{} by measuring a Placeable full size while placing it with size to crop such as

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

        val placeable = measurable.measure(
            constraints
        )

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

And since we clip the area out of layout's width we only have 58.dp composable that draws from (0,0) to position desired.

If you measure the composable with width specified above you need to also change alignment = Alignment.TopCenter because image uses Alignment.Center by default. Adding aligment to second Image will fix the issue.

在Jetpack Compose中剪裁图像

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")
}

You can also use Canvas or Modifier.drawBehind to achieve same result . The thing to consider we clip to rect in desired size while drawing intrinsic width which is the width of actual Painter or Bitmap while we get height from 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")

huangapple
  • 本文由 发表于 2023年5月10日 14:43:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/76215561.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定