Android Compose点击事件“黑洞”

背景

项目中有几个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内部实现,

  1. 将内部Button始终设置为enable=true
  2. 根据条件提供不同状态的色值、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简化调用。

相关推荐
问心无愧051341 分钟前
ctf show web入门111
android·前端·笔记
ha_lydms7 小时前
AnalyticDB分区、分布键性能优化
android·大数据·分布式·性能优化·分布式计算·分区·analyticdb
星辰7 小时前
Ijkplayer重新编译支持h264裸流
android
测试开发-学习笔记8 小时前
Android studio安装
android·ide·android studio
宋拾壹8 小时前
同时添加多个类目
android·开发语言·javascript
●VON9 小时前
AtomGit Flutter鸿蒙客户端:数据模型
android·服务器·安全·flutter·harmonyos·鸿蒙
火柴就是我9 小时前
记录一个文本随手指缩放的功能
android
Zender Han10 小时前
Android APK 签名 v1、v2、v3、v4 有什么区别?
android
神仙别闹10 小时前
基于 PHP + MySQL学生信息管理系统
android·mysql·php
墨狂之逸才11 小时前
Android 保活机制详解 —— 从概念到实践
android