[英]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.