我们将使用FloatingActionButton()
对于这个本地示例,因为它支持我们需要考虑的几乎所有选项,但这应该适用于任何可组合项。为了方便起见,我用这个解决方案包装了几个更常见的解决方案,并将它们组装在这个 GitHub 要点 https://gist.github.com/zed-alpha/3dc931720292c1f3ff31fa6a130f52cd,如果你想尝试一些除了FloatingActionButton()
.
我们希望我们的包装器 Composable 充当直接替代品,因此它的参数列表和默认值是从FloatingActionButton()
:
@Composable
fun ClippedShadowFloatingActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
backgroundColor: Color = MaterialTheme.colors.secondary,
contentColor: Color = contentColorFor(backgroundColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
content: @Composable () -> Unit
) {
Layout(
{
ClippedShadow(
elevation = elevation.elevation(interactionSource).value,
shape = shape,
modifier = modifier
)
FloatingActionButton(
onClick = onClick,
modifier = modifier,
interactionSource = interactionSource,
shape = shape,
backgroundColor = backgroundColor,
contentColor = contentColor,
elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp),
content = content
)
},
modifier
) { measurables, constraints ->
require(measurables.size == 2)
val shadow = measurables[0]
val target = measurables[1]
val targetPlaceable = target.measure(constraints)
val width = targetPlaceable.width
val height = targetPlaceable.height
val shadowPlaceable = shadow.measure(Constraints.fixed(width, height))
layout(width, height) {
shadowPlaceable.place(0, 0)
targetPlaceable.place(0, 0)
}
}
}
我们基本上是在包装一个FloatingActionButton()
以及我们的副本影子可组合项Layout()
针对设置进行了优化。大多数参数原封不动地传递给包装的FloatingActionButton()
除了elevation
,我们将其归零以禁用固有阴影。相反,我们直接指向我们的ClippedShadow()
适当的原始高程值,此处使用以下公式计算FloatingActionButtonElevation
and InteractionSource
参数。更简单的可组合项,例如Card()
将具有无状态海拔值Dp
可以直接通过。
ClippedShadow()
本身就是另一种习俗Layout()
,但没有内容:
@Composable
fun ClippedShadow(elevation: Dp, shape: Shape, modifier: Modifier = Modifier) {
Layout(
modifier
.drawWithCache {
// Naive cache setup similar to foundation's Background.
val path = Path()
var lastSize: Size? = null
fun updatePathIfNeeded() {
if (size != lastSize) {
path.reset()
path.addOutline(
shape.createOutline(size, layoutDirection, this)
)
lastSize = size
}
}
onDrawWithContent {
updatePathIfNeeded()
clipPath(path, ClipOp.Difference) {
[email protected] /cdn-cgi/l/email-protection()
}
}
}
.shadow(elevation, shape)
) { _, constraints ->
layout(constraints.minWidth, constraints.minHeight) {}
}
}
我们只需要它的影子和Canvas
访问,我们通过两个简单的方法获得Modifier
扩展。drawWithCache()
让我们保持简单Path
我们使用缓存来剪辑和恢复整个内容绘制,以及shadow()
这是不言自明的。将此可组合项分层放置在目标后面,并禁用其自身的阴影,我们就可以获得所需的效果:
正如问题中,前三个是Card()
, FloatingActionButton()
, and ExtendedFloatingActionButton()
,但包含在我们的修复中。为了证明InteractionSource
/elevation 重定向按预期工作,这个简短的动图 https://i.stack.imgur.com/48QVo.gif显示两个FloatingActionButton()
并排具有完全透明的背景;右边的已经应用了我们的修复。
对于上面修复图像中的第四个示例,我们使用了独奏ClippedShadow()
,只是为了说明它也可以单独工作:
ClippedShadow(
elevation = 10.dp,
shape = RoundedCornerShape(10.dp),
modifier = Modifier.size(FabSize)
)
就像常规的可组合项一样,它应该适用于任何Shape
这对于当前 API 级别有效。任意凸Path
s 适用于所有相关版本,从 API 级别 29 (Q) 开始,凹面版本也适用。