Compose Indication:打造獨特點擊效果的秘密武器
在Compose開發中,大家可能都碰到過Indication,不少人第一次接觸它,是在想去掉Material默認的點擊水波紋效果的時候。要是在AI工具里搜“怎么去掉水波紋效果”,會得到這樣一段代碼:
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,它能實現更高效的指示效果。Indication一般通過LocalIndication在整個層級結構中提供,開發者可以自定義LocalIndication,改變組件(比如clickable
)的默認指示效果。從這里能看出,Indication就是用來實現點擊、聚焦、拖動等組件效果的,有點像傳統View系統里的selector,但它的功能可要強大得多。
在傳統View系統里,selector能根據組件的不同狀態,指定不同的資源或顏色。不過,要實現交互效果,得知道組件的當前狀態。而Indication本身沒辦法獲取組件的交互狀態,這時就需要Interaction
來幫忙了。
在很多情況下,開發普通組件時,我們不用關心Compose組件是怎么解讀用戶操作的。比如創建一個Button
,通過Modifier.clickable
就能判斷用戶有沒有點擊,設置好onClick
代碼就行,不用管是點擊屏幕還是用鍵盤操作。但要是想自定義組件對用戶行為的響應方式,Interaction
就派上用場了。
當用戶和界面組件交互時,系統會生成很多Interaction
事件。比如用戶點擊按鈕,按鈕會生成PressInteraction.Press
;在按鈕范圍內松開手指,會生成PressInteraction.Release
,表示點擊完成;要是手指拖出按鈕范圍再松開,就會生成PressInteraction.Cancel
,代表點擊取消。這些互動事件沒有預設的含義,也不解讀操作順序和優先級。
如果想跟蹤互動來擴展組件功能,比如讓按鈕按下時變色,最簡單的辦法就是觀察互動狀態。InteractionSource
提供了很多方法來獲取各種互動狀態,像調用InteractionSource.collectIsPressedAsState()
,就能知道按鈕有沒有被按下:
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
,又能繪制視覺效果,所以簡單場景下,用Modifier.clickable
就行,不一定非要Modifier.indication
。
來看個例子,把點擊縮放效果用Indication實現,步驟如下:
- 創建負責應用縮放效果的
Modifier.Node
。這個節點要觀察互動來源,和之前的示例類似,但它會直接啟動動畫,而不是把互動轉成狀態。節點需要實現DrawModifierNode
,重寫ContentDrawScope#draw()
,用Compose的圖形API渲染縮放效果,調用drawContent()
繪制應用Indication
的組件,注意一定要調用,不然組件不會顯示。
private class ScaleNode(private val interactionSource: InteractionSource) :Modifier.Node(), DrawModifierNode {var currentPressPosition: Offset = Offset.Zeroval animatedScalePercent = Animatable(1f)private suspend fun animateToPressed(pressPosition: Offset) {currentPressPosition = pressPositionanimatedScalePercent.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) {this@draw.drawContent()}}
}
- 創建
IndicationNodeFactory
,它的任務就是創建新節點實例。如果沒有配置參數,工廠可以是個對象:
object ScaleIndication : IndicationNodeFactory {override fun create(interactionSource: InteractionSource): DelegatableNode {return ScaleNode(interactionSource)}override fun equals(other: Any?): Boolean = other === ScaleIndicationoverride fun hashCode() = 100
}
Modifier.clickable
內部用了Modifier.indication
,要讓組件帶有ScaleIndication
的點擊效果,直接把Indication
作為clickable
的參數就行:
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能定義一套交互效果并應用到各種組件上,如果項目里有標準的交互效果設計,用Indication準沒錯。歡迎大家一起交流,有問題可以在評論區留言或者私信我!