[英]How to clip or cut a Composable?
如何剪辑或剪切可组合内容以使图像、按钮或可组合具有自定义形状? 这个问题与使用Modifier.clip()
无关,更像是使用替代方法完成任务,这些方法允许不可能的结果,或者难以创建像云或 Squircle 这样的形状。
这是分享你的知识,问答式的问题,灵感来自 M3 BottomAppBar 或 BottomNavigation 没有切口形状,找不到问题,并且在这个问题中绘制一个松鼠形状很困难。
更多更好的剪裁或自定义形状和可组合项的方法非常受欢迎。
在不需要创建自定义 Composable 的情况下实现剪切或剪辑 Composable 的方法之一是使用
Modifier.drawWithContent{}
具有图层和BlendMode
或PorterDuff 模式。
使用 Jetpack Compose 以使这些模式正常工作,您需要将 alpha 设置为小于 1f 或使用 Layer 作为答案。
我使用层解决方案 go 因为我不想更改内容 alpha
fun ContentDrawScope.drawWithLayer(block: ContentDrawScope.() -> Unit) {
with(drawContext.canvas.nativeCanvas) {
val checkPoint = saveLayer(null, null)
block()
restoreToCount(checkPoint)
}
}
块 lambda 是绘制 scope 用于Modifier.drawWithContent{}
进行剪辑
以及进一步简化的另一个扩展
fun Modifier.drawWithLayer(block: ContentDrawScope.() -> Unit) = this.then(
Modifier.drawWithContent {
drawWithLayer {
block()
}
}
)
@Composable private fun WhoAteMyButton() { val circleSize = LocalDensity.current.run { 100.dp.toPx() } Box( modifier = Modifier.fillMaxWidth().drawWithLayer { // Destination drawContent() // Source drawCircle( center = Offset(0f, 10f), radius = circleSize, blendMode = BlendMode.SrcOut, color = Color.Transparent ) } ) { Button( modifier = Modifier.padding(horizontal = 10.dp).fillMaxWidth(), onClick = { /*TODO*/ }) { Text("Hello World") } } }
我们只是简单地画了一个圆,但因为BlendMode.SrcOut
与目的地的交点被移除了。
对于松鼠按钮,我从 web 找到了一张图片
并使用此图像剪切按钮和图像
@Composable private fun ClipComposables() { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { val imageBitmap = ImageBitmap.imageResource(id = R.drawable.squircle) Box(modifier = Modifier.size(150.dp).drawWithLayer { // Destination drawContent() // Source drawImage( image = imageBitmap, dstSize = IntSize(width = size.width.toInt(), height = size.height.toInt()), blendMode = BlendMode.DstIn ) } ) { Box( modifier = Modifier.size(150.dp).clickable { }.background(MaterialTheme.colorScheme.inversePrimary), contentAlignment = Alignment.Center ) { Text(text = "Squircle", fontSize = 20.sp) } } Box(modifier = Modifier.size(150.dp).drawWithLayer { // Destination drawContent() // Source drawImage( image = imageBitmap, dstSize = IntSize(width = size.width.toInt(), height = size.height.toInt()), blendMode = BlendMode.DstIn ) } ) { Image( painterResource(id = R.drawable.squirtle), modifier = Modifier.size(150.dp), contentScale = ContentScale.Crop, contentDescription = "" ) } } }
这里有两点需要注意
1- 混合模式是BlendMode.DstIn
,因为我们希望Destination
的纹理具有Source
的形状 2- 在 ContentDrawScope 内使用 dstSize 绘制图像以匹配可组合大小。 默认情况下,它使用上面发布的 png 大小绘制。
val cutCornerShape = CutCornerShape(50)
val outline = cutCornerShape.createOutline(
Size(shapeSize, shapeSize),
LocalLayoutDirection.current,
density
)
这段代码有点长,但我们基本上像往常一样创建一个形状并创建一个轮廓来剪辑
withTransform(
{
translate(
left = (width - outlineWidth) / 2,
top = -outlineHeight / 2
)
}
) {
drawOutline(
outline = outline,
color = Color.Transparent,
blendMode = BlendMode.Clear
)
}
在剪裁之前,我们将这个形状部分向上移动一半的高度,只剪掉一半的轮廓
icons.forEachIndexed { index, imageVector: ImageVector ->
if (index == 2) {
Spacer(modifier = Modifier.weight(1f))
BottomNavigationItem(
icon = { Icon(imageVector, contentDescription = null) },
label = null,
selected = selectedIndex == index,
onClick = {
selectedIndex = index
}
)
} else {
BottomNavigationItem(
icon = { Icon(imageVector, contentDescription = null) },
label = null,
selected = selectedIndex == index,
onClick = {
selectedIndex = index
}
)
}
}
还有一个底部导航,例如底部应用程序栏,将孩子放在两侧,我使用了 Spacer
icons.forEachIndexed { index, imageVector: ImageVector -> if (index == 2) { Spacer(modifier = Modifier.weight(1f)) BottomNavigationItem( icon = { Icon(imageVector, contentDescription = null) }, label = null, selected = selectedIndex == index, onClick = { selectedIndex = index } ) } else { BottomNavigationItem( icon = { Icon(imageVector, contentDescription = null) }, label = null, selected = selectedIndex == index, onClick = { selectedIndex = index } ) } }
然后我们只需添加一个 FloatingActionButton,我使用了偏移量,但您可以创建一个更大的父级并将我们的自定义底部导航和按钮放入其中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.