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简化调用。

相关推荐
李艺为2 小时前
Android Studio使用switch匹配资源id时报需要常量表达式解决办法
android
YaBingSec3 小时前
玄机靶场-2024ccb初赛sc05 WP
android·运维·网络·笔记·安全·ssh
常利兵3 小时前
解锁Android嵌入式照片选择器,让你的App体验丝滑起飞
android
峥嵘life4 小时前
Android 切换用户后无法获取 MAC 地址分析解决
android·python·macos
JJay.4 小时前
Android BLE 为什么连上了却收不到数据
android
歪楼小能手4 小时前
Android16在开机向导最后添加一个声明界面
android·java·平板
夏沫琅琊4 小时前
Android联系人导入导出
android·kotlin
zh_xuan4 小时前
把Android Library 上传到github并在工程中引用该远程仓库
android·github·远程仓库
诸神黄昏EX4 小时前
Android Google MADA
android
盖丽男4 小时前
使用 GitHub Actions 自动打包 Android APK
android·github