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的作用,它可以定义一套交互效果然后应用到任何组件上。如果你们的交互设计有一套或多套标准的交互效果,那就不要犹豫直接使用它。
欢迎大家一起交流,有问题可以评论或私信!