如何实现一个Composable被另一个Composable裁剪
实现Composable元素交叉时的边框裁剪效果
问题说明
我有两个带透明背景的Composable元素(带边框、透明背景和文本的简单Box),可在屏幕上移动。需求是当两者交叉时,当前移动的元素被另一个的边框裁剪,预期效果如下:
尝试过clipPath和Region.Op但未找到获取已绘制元素路径的方法,以下是点击后第二个元素折叠到第一个元素下方的示例代码:
enum class State { Collapsed, Expanded } @Composable fun Main() { val cardSize = 100.dp var currentState by remember { mutableStateOf(State.Collapsed) } val transition = updateTransition(targetState = currentState, label = "expand_transition") val animateDpAsState by transition.animateDp( targetValueByState = { state -> when (state) { State.Expanded -> cardSize + 8.dp State.Collapsed -> 8.dp } }, label = "" ) Box( modifier = Modifier .width(cardSize + animateDpAsState) .clickable { currentState = if (currentState == State.Collapsed) { State.Expanded } else { State.Collapsed } } ) { Element(i = 1) Element( modifier = Modifier .offset(x = animateDpAsState + 4.dp) .scale(0.90f), i = 2 ) } } @Composable fun Element(modifier: Modifier = Modifier, i: Int) { Box( modifier = modifier .size(100.dp) .padding(8.dp) .border(2.dp, Color.Black), contentAlignment = Alignment.Center ) { Text(text = "ITEM $i") } }
解决方法
核心思路是通过自定义Modifier获取目标元素的布局边界,对移动元素进行反向裁剪:
1. 自定义裁剪Modifier
编写clipToOtherElement,利用drawWithContent和clipPath实现区域裁剪:
fun Modifier.clipToOtherElement(otherElementBounds: Rect) = this.drawWithContent { // 适配Element的padding和border,调整裁剪区域到边框内边缘 val inset = 8.dp.toPx() + 2.dp.toPx() val adjustedBounds = otherElementBounds.inset(inset, inset) clipPath( path = Path().apply { addRect(adjustedBounds) }, clipOp = ClipOp.Difference ) { drawContent() } }
2. 获取固定元素的布局边界
使用onGloballyPositioned监听第一个元素的位置,保存其布局边界:
@Composable fun Main() { val cardSize = 100.dp var currentState by remember { mutableStateOf(State.Collapsed) } val transition = updateTransition(targetState = currentState, label = "expand_transition") val animateDpAsState by transition.animateDp( targetValueByState = { state -> when (state) { State.Expanded -> cardSize + 8.dp State.Collapsed -> 8.dp } }, label = "" ) // 保存第一个元素的布局边界 val firstElementBounds = remember { mutableStateOf(Rect.Zero) } Box( modifier = Modifier .width(cardSize + animateDpAsState) .clickable { currentState = if (currentState == State.Collapsed) { State.Expanded } else { State.Collapsed } } ) { Element(i = 1) .onGloballyPositioned { coords -> firstElementBounds.value = coords.boundsInParent() } Element( modifier = Modifier .offset(x = animateDpAsState + 4.dp) .scale(0.90f) // 应用裁剪,排除第一个元素的区域 .clipToOtherElement(firstElementBounds.value), i = 2 ) } }
这样当第二个元素移动与第一个元素交叉时,重叠部分会被精确裁剪,完全匹配预期效果。
内容的提问来源于stack exchange,提问作者shagi
相关产品推荐
相关产品推荐

