Compose 自定义 - 处理交互 Interaction

关注目前处于实验性的更简单实现方式 androidx.compose.foundation.style

属于手势的最顶层API,更底层的自定义方式详见

官方页面

一、概念

针对的是那些自带交互的组件,通过将内部交互事件分享出来供外部观察,来对该组件应用不同的外观内容,或让其它组件产生联动效果。但这样要手动给每个组件应用效果,更通用的可重复应用的效果详见Indication

二、创建交互源对象

InteractionSource 提供交互的只读流,而 MutableInteractionSource 则允许往数据流中添加新交互,它俩都是接口需要通过工厂函数返回实现类。

|----------------------------|--------------------------------------------------------------------|
| MutableInteractionSource() | fun MutableInteractionSource(): MutableInteractionSource 通过工厂函数创建。 |

Kotlin 复制代码
val interactionSource = remember { MutableInteractionSource() }

三、传参给组件/修饰符

传参后,即可观察该组件的交互。

3.1 自带交互的标准组件

Kotlin 复制代码
val interactionSource = remember { MutableInteractionSource() }

Button(
    interactionSource = interactionSource,
    onClick = {}
) {...}

3.2 交互类型的Modifier

|--------|--------------------------|--------------------------|--------------------------|
| 高级别API | Modifier.clickable() 可以一次全处理。 |||
| 低级别API | Modifier.draggable() 拖拽。 | Modifier.hoverable() 悬停。 | Modifier.focusable() 焦点。 |

Kotlin 复制代码
val interactionSource = remember { MutableInteractionSource() }

Box(Modifier
    ..draggable(interactionSource = interactionSource)
    .hoverable(interactionSource = interactionSource)
    .focusable(interactionSource = interactionSource)
    .clickable(
        interactionSource = interactionSource,
        onClick = {}
    )
) {...}

四、观察交互

4.1 交互状态(高级别API)

对于单个交互类型的收集,有简单的封装方法调用,底层将流转为了状态,返回的状态只有两个值 true/false。

|----|-------------------------------------------------------------------------------|
| 点按 | @Composable fun InteractionSource.collectIsPressedAsState(): State<Boolean> |
| 拖拽 | @Composable fun InteractionSource.collectIsDraggedAsState(): State<Boolean> |
| 悬停 | @Composable fun InteractionSource.collectIsHoveredAsState(): State<Boolean> |
| 焦点 | @Composable fun InteractionSource.collectIsFocusedAsState(): State<Boolean> |

Kotlin 复制代码
val interactionSource = remember { MutableInteractionSource() }
val isPress by interactionSource.collectIsPressedAsState()
Text(
    color = if (isPress) Color.Black else Color.Blue,
    text = ""
)

4.2 交互流(低级别API)

若同时需要两种状态,Compose 会执行大量重复工作,且无法保证互动顺序正确,这种时候就需要手动收集数据流了。

交互都是成对出现,结束或取消的交互中都带有一个属性,这个属性是对应的那个起始交互。

|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| PressInteraction 点按 | interface PressInteraction : Interaction { class Press(val pressPosition: Offset) : PressInteraction class Release(val press: Press) : PressInteraction class Cancel(val press: Press) : PressInteraction } |
| DragInteraction 拖拽 | interface DragInteraction : Interaction { class Start : DragInteraction class Stop(val start: Start) : DragInteraction class Cancel(val start: Start) : DragInteraction } |
| HoverInteraction 悬停 | interface HoverInteraction : Interaction { class Enter : HoverInteraction class Exit(val enter: Enter) : HoverInteraction } |
| FocusInteraction 焦点 | interface FocusInteraction : Interaction { class Focus : FocusInteraction class Unfocus(val focus: Focus) : FocusInteraction } |

举例:只需要按下和拖动,从Button读取,Text响应。

Kotlin 复制代码
@Composable
fun InteractionDemo() {
    //创建交互对象
    val interactionSource = remember { MutableInteractionSource() }
    //存储点按和拖动交互
    val myInteractions = remember { mutableStateListOf<Interaction>() }
    //收集流
    LaunchedEffect(interactionSource) {
        interactionSource.interactions.collect { interaction ->
            when (interaction) {
                //发生新的按下互动时立即将其添加到列表中
                is PressInteraction.Press -> { myInteractions.add(interaction) }
                //在互动结束时从列表中移除互动
                is PressInteraction.Release -> { myInteractions.remove(interaction.press) }
                is PressInteraction.Cancel -> { myInteractions.remove(interaction.press) }
                //拖动同理
                is DragInteraction.Start -> { myInteractions.add(interaction) }
                is DragInteraction.Stop -> { myInteractions.remove(interaction.start) }
                is DragInteraction.Cancel -> { myInteractions.remove(interaction.start) }
            }
        }
    }
    //组件中使用自定义交互
    //判断是否处于按下或拖动状态,只需要检查列表是否为空
    val isPress = myInteractions.isNotEmpty()
    //获取最近一次互动,只需要查看最后列表的最后一项
    val lastInteraction = when (myInteractions.lastOrNull()) {
        is DragInteraction.Start -> "拖动"
        is PressInteraction.Press -> "点按"
        else -> "无"
    }
    //赋值给Button
    Button(
        interactionSource = interactionSource,
        onClick = {}
    ) {
        //Text读取
        Text(
            color = if (isPress) Color.Black else Color.Blue,
            text = ""
        )
    }
}

五、自定义组件

构建自定义的组件或修饰符,通过在参数列表暴露 InteractionSource 或 MutableInteractionSource 类型,将交互源从内部提升出去。内部往交互流中发射交互,外部收集交互流来对该组件或其它组件应用视觉反馈。

5.1 基于标准组件封装

按钮点击后显示购物车图标。

Kotlin 复制代码
@Composable
fun MyButton(
    interactionSource: MutableInteractionSource? = null,
) {
    //鼠标悬停同理,只是把这里改成 collectIsHoveredAsState()
    val isPressed = interactionSource?.collectIsPressedAsState()?.value ?: false
    Button(
        interactionSource = interactionSource,
        onClick = {}
    ) {
        AnimatedVisibility(isPressed) {
            if (isPressed) {
                Icon(
                    modifier = Modifier.padding(end = 10.dp),
                    imageVector = Icons.Filled.ShoppingCart,
                    contentDescription = null
                )
            }
            Text("Add to cart")
        }
    }
}

5.2 自定义Modifier

对于在聚焦状态下绘制边框的修饰符,只需要观察 Interactions。对于处理悬停事件的修饰符,需要发出 Interactions。

Kotlin 复制代码
fun Modifier.focusBorder(interactionSource: InteractionSource): Modifier {
    // ...
}

fun Modifier.hover(interactionSource: MutableInteractionSource, enabled: Boolean): Modifier {
    // ...
}

5.3 完全自定义组件

根据更底层的手势API,通过 MutableInteractionSource 的 emit() 和 tryEmit() 发送交互。

相关推荐
nbsaas-boot2 小时前
SQL JOIN 图解说明
android·数据库·sql
Albert Tan2 小时前
Oracle EBS PO 报错 -- 非买手
android·数据库·oracle
00后程序员张2 小时前
iOS 应用的 HTTPS 连接端口在网络抓包调试中有什么作用
android·网络·ios·小程序·https·uni-app·iphone
m0_738120722 小时前
网络安全编程——PHP基础Session详细讲解
android·网络·windows·安全·web安全·php
binderIPC3 小时前
Android项目中FFmpeg的.so包使用详情
android·ffmpeg
2501_915909063 小时前
iPhone 手机日志实时查看,开发和测试中常用的几种方法
android·ios·智能手机·小程序·uni-app·iphone·webview
ClassOps3 小时前
记录 Android WebView内核更新,安全区 和 Insets 消费问题
android·webview·compose
ysh98883 小时前
2025年 Android Studio修仙传(kotlin版):基础篇
android·kotlin·android studio
XiaoLeisj3 小时前
Android 网络编程入门到实战:HttpURLConnection、JSON 处理、OkHttp 与 Retrofit2
android·网络·okhttp·json·gson·retrofit2·jsonobjecy