背景
项目中有几个Composable Button,产品要求点击置灰状态的FollowStatusButton
按钮时,弹一个toast。
意外翻车
作为View体系过来人,这个问题一看就很简单,按钮置灰状态时,为其父布局设置点击事件即可,demo代码如下:
kotlin
@Composable
@Preview
fun ButtonDemo(modifier: Modifier = Modifier) {
val context = LocalContext.current
Box(
modifier = Modifier
.background(color = Color.Yellow)
.clickable {
Toast.makeText(context, "Box clicked!", Toast.LENGTH_SHORT).show()
}
.padding(30.dp),
) {
Button(
onClick = {
Toast.makeText(context, "Button clicked!", Toast.LENGTH_SHORT).show()
},
enabled = false,
) {
Text(text = "Disabled Button")
}
}
}
只有按钮外面的黄色区域才能响应点击事件,按钮区域的点击事件被吃掉了,没传递到父布局,这和View的事件传递默认行为是不一样的!
寻找原因
经过一番查找,有人提出了相同的问题issuetracker.google.com/issues/2397...
Compose就是这样设计的,当Button enable=false
时, 这块区域就像黑洞一样,点击事件都会被吞掉。
探索解决方案
那么我们就只能另外想办法了,我们可以修改FollowStatusButton
内部实现,
- 将内部
Button
始终设置为enable=true
- 根据条件提供不同状态的色值、click listener
这样能满足条件,但它侵入原有FollowStatusButton
代码,如果其他组件也要类似处理,我们需要对每个组件进行改造,这将极大地增加我们的工作量。
有没有一种方案能像View事件传递那样,不侵入原有组件代码,也能实现类似效果? 我们可以将组件放在Box
里面,再添加一个和FollowStatusButton
同等大小 可点击 的组件,类似的demo代码如下:
kotlin
@Composable
@Preview
fun ButtonDemo(modifier: Modifier = Modifier) {
val context = LocalContext.current
val contentClickEnabled = false
ClickableLayer(
contentClickEnabled = contentClickEnabled,
onDisabledContentClick = {
Toast.makeText(context, "ClickableLayer clicked!", Toast.LENGTH_SHORT).show()
},
content = {
// 包装任意现有组件,调用方式不变
Button(
onClick = {
Toast.makeText(context, "Button clicked!", Toast.LENGTH_SHORT).show()
},
enabled = contentClickEnabled,
) {
Text(text = "Disabled Button")
}
}
)
}
@Composable
fun ClickableLayer(
modifier: Modifier = Modifier,
contentClickEnabled: Boolean,
onDisabledContentClick: () -> Unit,
content: @Composable () -> Unit,
) {
Box(modifier = modifier) {
content()
if (!contentClickEnabled) {
Box(
modifier = Modifier
.matchParentSize() // 构建与content等宽高的点击区域
.clickable(
// 去掉默认的按压效果
indication = null,
interactionSource = remember {
MutableInteractionSource()
},
onClick = onDisabledContentClick,
),
)
}
}
}
至此,遇到类似场景就可以用ClickableLayer
来包裹组件了,我们无需关心和修改组件内部逻辑,降低了心智负担。
总结
本文从需求出发,基于已有开发经验开始构建代码,发现Compose点击事件与View体系的差异:组件在enable=false时,导致其父组件也无法响应点击事件。通过探索给出了相对优雅的解决方案,运用matchParentSize
构建了一个等宽高的点击区域,封装成ClickableLayer
简化调用。