Compose Indication 实现点击效果

Indication是什么

多数人第一次接触 Indication,应该是为了移除 Material 默认点击水波纹效果。在 AI 工具里问:如何移除水波纹效果,你将会得到这段代码:

kotlin 复制代码
 Box(
        modifier = Modifier
            .size(200.dp)
            .clickable(
                // 去除指示效果
                indication = null, 
                interactionSource = null
            ) {
                // 点击事件处理逻辑
            },
        contentAlignment = Alignment.Center
    ) {
        Text(text = "No Ripple Click", color = Color.Black)
    }

将 indication 参数指定为 null,则移除了水波纹效果。

它是怎么做到的呢?我们先来看下 Indication 的注释

Indication 表示在某些交互发生时出现的视觉效果。例如:当组件被按下时显示涟漪效应,或者当组件被聚焦时显示高亮。 要实现自己的Indication ,请参见IndicationNodeFactory ------一个优化的指示,允许比已弃用的memorberupdatedinstance更有效的实现。 Indication 通常是通过LocalIndication在整个层次结构中提供的。你可以为LocalIndication提供一个自定义的Indication,以改变用于组件(如clickable)的默认指示。

从注释我们可以看出来,Indication 就是用来实现点击、聚焦或拖动时的组件效果。这其实就相当于是传统 View 系统下的 selector,对吧?不过 Indication 的功能要强大得多!

xml 复制代码
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
           android:drawable="@color/colorGreen" />
    <item android:state_pressed="false"
           android:drawable="@color/colorYellow" />
     <!-- This is the default state item, it is same as <item android:drawable="@color/colorRed" />
          This item should be written in the end. -->
     <item android:state_window_focused="true" android:drawable="@color/colorRed"/>
</selector>

selector 可以通过组件不同的状态指定不同的资源、颜色。而要实现交互的效果,那就需要知道组件的当前状态。但是,Indication 本身是没法知道组件的交互状态的。这时候我们就需要另一个工具 Interaction

Interaction「交互/互动」 是什么

在许多情况下,你无需了解 Compose 组件如何解读用户互动。例如,Button 通过 Modifier.clickable 来判断用户是否点击了按钮。如果你要在应用中添加一个普通的按钮,可以定义该按钮的 onClick 代码,而 Modifier.clickable 将会适时运行该代码。这意味着,你不必知道用户是点按了屏幕还是通过键盘选择了该按钮;Modifier.clickable 会知道用户执行了点击操作,并通过运行 onClick 代码来做出响应。

不过,如果你想自定义界面组件对用户行为的响应方式,则可能需要详细了解背后的运作原理。Interaction 就派上了用场。

当用户与界面组件互动时,系统会生成多个 Interaction 事件来表示其行为。例如,如果用户轻触某个按钮,该按钮会生成 PressInteraction.Press。如果用户在按钮范围内松开手指,系统就会生成 PressInteraction.Release,让按钮知道点击已完成。相反,如果用户将手指拖动到按钮范围外再松开,按钮就会生成 PressInteraction.Cancel,表示按下按钮的操作已取消,并未完成。

这些互动无预设立场。也就是说,这些低级别互动事件不会解读用户操作的含义或序列,也不会解读用户操作的优先顺序。

互动状态

你可以同时自行跟踪互动,以扩展组件的内置功能。例如,你可能希望某个按钮在被按下时改变颜色。最简单的互动跟踪方法就是观察相应的互动状态。**InteractionSource 提供了多种方法来获取各种互动状态。例如,如果你想查看是否按下了特定按钮,可以调用其 InteractionSource.collectIsPressedAsState() 方法:

kotlin 复制代码
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()

Button(
    onClick = { /* do something */ },
    interactionSource = interactionSource
) {
    Text(if (isPressed) "Pressed!" else "Not pressed")
}

collectIsPressedAsState() 之外,Compose 还提供了 collectIsFocusedAsState()collectIsDraggedAsState()collectIsHoveredAsState()。这些方法实际上是基于较低级别 InteractionSource API 的便捷方法。在某些情况下,建议你直接使用这些较低级别的函数。

关于Interaction 暂时就说这么多,大家感兴趣的可以自己去官网学习。此处大家只需要知道其作用就行,我们再回到 Indication

使用 Indication 创建和应用可重复使用的自定义效果

  • IndicationNodeFactory:用于创建 Modifier.Node 实例的工厂, 为组件渲染视觉效果对于 它可以是单一实例(对象),并且可以在作用整个应用

    这些实例可以是有状态实例,也可以是无状态实例。由于它们是 组件,它们可以从 CompositionLocal 检索值,以更改方式 与其他所有组件一样 Modifier.Node

  • Modifier.indication: 一个修饰符,用于绘制Indication 组件。Modifier.clickable 和其他高级互动修饰符 直接接受指示参数,这样它们不仅能发出 Interaction,但也可以为其 Interaction 绘制视觉效果 emit。因此,在简单的情况下,你可以只使用 Modifier.clickable,而不使用 需要 Modifier.indication

来看一个示例,比如将上面点击缩放的示例转为 Indication,具体步骤:

1.创建负责应用缩放效果的 Modifier.Node。 附加后,节点会观察互动来源,这与之前表示的类似 示例。唯一的区别在于,它会直接启动动画 而不是将传入的互动转换为状态。

节点需要实现 DrawModifierNode,以便可以替换 ContentDrawScope#draw(),然后使用相同的绘图渲染缩放效果 命令与 Compose 中的任何其他图形 API 相同。

调用 ContentDrawScope 接收器提供的 drawContent() 将绘制 实际应该应用 Indication 的组件,只需 需要在缩放转换中调用此函数。请确保您的 Indication 实现始终会在某个时间点调用 drawContent(); 否则,系统将不会绘制您应用 Indication 的组件。

kotlin 复制代码
private class ScaleNode(private val interactionSource: InteractionSource) :
    Modifier.Node(), DrawModifierNode {

    var currentPressPosition: Offset = Offset.Zero
    val animatedScalePercent = Animatable(1f)

    private suspend fun animateToPressed(pressPosition: Offset) {
        currentPressPosition = pressPosition
        animatedScalePercent.animateTo(0.9f, spring())
    }

    private suspend fun animateToResting() {
        animatedScalePercent.animateTo(1f, spring())
    }

    override fun onAttach() {
        coroutineScope.launch {
            interactionSource.interactions.collectLatest { interaction ->
                when (interaction) {
                    is PressInteraction.Press -> animateToPressed(interaction.pressPosition)
                    is PressInteraction.Release -> animateToResting()
                    is PressInteraction.Cancel -> animateToResting()
                }
            }
        }
    }

    override fun ContentDrawScope.draw() {
        scale(
            scale = animatedScalePercent.value,
            pivot = currentPressPosition
        ) {
            [email protected]()
        }
    }
}

2.创建 IndicationNodeFactory。它的唯一责任就是 新节点实例。由于没有 参数来配置指示,则工厂可以是对象:

kotlin 复制代码
object ScaleIndication : IndicationNodeFactory {
    override fun create(interactionSource: InteractionSource): DelegatableNode {
        return ScaleNode(interactionSource)
    }

    override fun equals(other: Any?): Boolean = other === ScaleIndication
    override fun hashCode() = 100
}

3.Modifier.clickable 在内部使用 Modifier.indication,因此 带有 ScaleIndication 的可点击组件,您只需提供 Indication 作为 clickable 的参数

kotlin 复制代码
Box(
    modifier = Modifier
        .size(100.dp)
        .clickable(
            onClick = {},
            indication = ScaleIndication,
            interactionSource = null
        )
        .background(Color.Blue),
    contentAlignment = Alignment.Center
) {
    Text("Hello!", color = Color.White)
}

如此之后,我们便完成了一个按住之后缩放的一个交互效果,你可以把这个 Indication 应用在任何 Composable 函数上。

这就是Indication的作用,它可以定义一套交互效果然后应用到任何组件上。如果你们的交互设计有一套或多套标准的交互效果,那就不要犹豫直接使用它。

欢迎大家一起交流,有问题可以评论或私信!

相关推荐
拉不动的猪4 分钟前
前端如何判断登录设备是移动端还是pc端
前端·javascript·css
小圆脸儿9 分钟前
通用组件库设计方案ui-components
前端·前端框架
拉不动的猪10 分钟前
刷刷题38(长连接 +切片上传)
前端·javascript·面试
二流小码农10 分钟前
鸿蒙开发:ArkTs语言注释
android·ios·harmonyos
二流小码农20 分钟前
鸿蒙开发:权限授权封装
android·ios·harmonyos
哀木26 分钟前
随笔之 react 接入 @xterm 的踩坑记录
前端
野生的程序媛41 分钟前
重生之我在学Vue--第13天 Vue 3 单元测试实战指南
前端·javascript·vue.js·单元测试
Aphasia3111 小时前
简单介绍清除浮动解决高度塌陷的四种方法✍🏻
前端·css
Captaincc2 小时前
这款堪称编程界的“自动驾驶”利器,集开发、调试、提 PR、联调、部署于一体
前端·ai 编程
CYRUS_STUDIO2 小时前
基于 Unicorn 实现一个轻量级的 ARM64 模拟器
android·逆向·汇编语言