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

相关推荐
F***74171 小时前
数据库课设---酒店管理系统(MySQL、VBNet)
android·数据库·mysql
踢球的打工仔1 小时前
PHP面向对象(5)
android·java·php
zhaoyufei13312 小时前
Android13删除Taskbar
android
6***B4814 小时前
存储过程(SQL)
android·数据库·sql
学困昇15 小时前
C++中的异常
android·java·c++
Jerry15 小时前
问题记录 - Android IdleHandler 没有执行
android
没有了遇见16 小时前
Android ButterKnife Android 35情况下 适配 Gradle 8.+
android
方白羽16 小时前
Android多层嵌套RecyclerView滚动
android·java·kotlin
菜就多学17 小时前
SurfaceControlViewHost 实现跨进程UI渲染
android·设计
2501_9151063218 小时前
iOS App 测试工具全景分析,构建从开发调试到线上监控的多阶段工具链体系
android·测试工具·ios·小程序·uni-app·iphone·webview