使用者介面元件可透過以下方式向裝置使用者提供意見回饋 回應使用者互動每個元件都有專屬的回應 互動 - 幫助使用者瞭解使用者互動行為。適用對象 舉例來說,如果使用者在裝置的觸控螢幕上輕觸按鈕, 可能以某種程度改變 例如加上醒目顯示顏色這項異動 讓使用者知道他們已輕觸按鈕。如果使用者不願意 這樣他們就會知道 否則這個按鈕就會啟動
![](https://developer.android.com/static/develop/ui/compose/images/touchinput/button-always-enabled.gif?authuser=0000&hl=zh-tw)
![](https://developer.android.com/static/develop/ui/compose/images/touchinput/button-reflects-state.gif?authuser=0000&hl=zh-tw)
Compose 手勢說明文件 會說明 Compose 元件會處理低階指標事件,例如指標移動和 點擊。Compose 預設會將這些低層級事件 互動程度較高的活動,舉例來說,一系列指標事件可能會 按下並放開按鈕。瞭解這些較高層級的抽象層 可協助您自訂 UI 回應使用者的方式。舉例來說 自訂元件外觀,決定使用者與使用者互動時的元件 或者只是想保留這類使用者動作的記錄這個 文件提供了修改標準 UI 元素所需的資訊 也可以自行設計
互動
在許多情況下,您不必瞭解 Compose 元件
或是解讀使用者互動方式舉例來說,Button
依賴
Modifier.clickable
瞭解使用者是否點選了按鈕。如果要加入的是
的按鈕,您可以定義該按鈕的 onClick
程式碼。
Modifier.clickable
會視情況執行該程式碼。也就是說
便能得知使用者是否輕觸螢幕
鍵盤;Modifier.clickable
會發現使用者已完成點擊,而且
就會回應您的 onClick
程式碼。
不過,如果您要自訂 UI 元件對使用者行為的回應, 可能還需要更瞭解背後的運作原理本節提供了 提供一些資訊
當使用者與 UI 元件互動時,系統會表示其行為
方法是
Interaction
敬上
事件。舉例來說,如果使用者輕觸按鈕,這個按鈕就會產生
PressInteraction.Press
。
當使用者在按鈕內舉起手指時,
PressInteraction.Release
、
通知按鈕點擊已經完成另一方面
使用者將手指拖曳到按鈕外,然後舉起手指,
產生
PressInteraction.Cancel
、
表示按下按鈕已取消,未完成。
這類互動無預設立場。也就是說 事件並非解讀使用者動作的意義 序列也不會解讀這類動作的優先順序 或執行其他動作
這類互動通常會成對,具有開始和結束。第二個
次互動包含第一個事件的參照。例如,如果使用者
輕觸按鈕並放開手指,觸控就會產生
PressInteraction.Press
敬上
而發布版本會產生
PressInteraction.Release
;
Release
具有 press
屬性,可識別
PressInteraction.Press
。
您可以觀察特定元件的互動情形,
InteractionSource
。InteractionSource
是以 Kotlin 為基礎
資料流,因此您可以透過相同的方式收集互動資料
您需要執行其他流程如要進一步瞭解這項設計決策
請參閱「Illuminating Interactions」這篇網誌文章。
互動狀態
您可能會想擴充元件的內建功能
自行追蹤互動情形舉例來說,假設您希望某個按鈕
按下後會變更顏色。如要追蹤互動情形,最簡單的方法是:
觀察適當的互動狀態。「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 為基礎。在某些情況下,
想直接使用這些較低層級的函式
例如,假設您需要知道使用者是否按下某個按鈕
。如果您同時使用 collectIsPressedAsState()
和 collectIsDraggedAsState()
,Compose 會執行大量重複的工作
並不保證您能以正確順序獲得所有互動。適用對象
遇到這種情況,建議您直接與
InteractionSource
。進一步瞭解如何追蹤互動情形
瞭解如何運用 InteractionSource
操作,請參閱「使用 InteractionSource
」一文。
下一節將說明如何使用及發出互動
InteractionSource
和 MutableInteractionSource
。
消耗並發出 Interaction
InteractionSource
代表 Interactions
的唯讀串流,而非
向 InteractionSource
發出 Interaction
。發出
Interaction
,您需要使用從MutableInteractionSource
InteractionSource
。
修飾符和元件可以使用、發出或消耗及發出 Interactions
。
以下各節說明如何使用及發出互動
修飾符和元件
使用修飾符範例
對於為聚焦狀態繪製邊框的修飾符,您只需要觀察
Interactions
,這樣可以接受 InteractionSource
:
fun Modifier.focusBorder(interactionSource: InteractionSource): Modifier { // ... }
從函式簽章中可明確指出這個修飾符是「取用端」,
可以使用 Interaction
,但無法發出。
產生修飾符範例
如果修飾符會處理 Modifier.hoverable
等懸停事件,則
必須發出 Interactions
,並接受 MutableInteractionSource
做為
參數:
fun Modifier.hover(interactionSource: MutableInteractionSource, enabled: Boolean): Modifier { // ... }
這個修飾符是「生產端」,可以使用提供的
MutableInteractionSource
可在懸停時發出 HoverInteractions
,或
不會被遮住
建構消耗及產生的元件
Material Button
等高階元件同時擔任生產者和
。可處理輸入和焦點事件,並變更外觀
以回應這些事件, 像是顯示漣漪效果或以動畫形式呈現
因此,它們會直接將 MutableInteractionSource
做為
參數,讓您可以提供自己的記住的執行個體:
@Composable fun Button( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, // exposes MutableInteractionSource as a parameter interactionSource: MutableInteractionSource? = null, elevation: ButtonElevation? = ButtonDefaults.elevatedButtonElevation(), shape: Shape = MaterialTheme.shapes.small, border: BorderStroke? = null, colors: ButtonColors = ButtonDefaults.buttonColors(), contentPadding: PaddingValues = ButtonDefaults.ContentPadding, content: @Composable RowScope.() -> Unit ) { /* content() */ }
如此一來,即可提升
MutableInteractionSource
從元件中觀察所有
元件產生的 Interaction
。您可以使用這項功能來控制
或者 UI 中任何其他元件的外觀。
如要自行建立互動式高階元件,建議您
透過這種方式將 MutableInteractionSource
公開為參數。側邊
遵循狀態提升最佳做法,也能讓您更輕鬆地
控制元件視覺狀態的方式和
狀態 (例如啟用狀態) 可讀取及控制。
Compose 採用分層架構方法,
高階 Material 元件是以基礎建築物為基礎
產生 Interaction
所需的區塊,而這些區塊需要控制漣漪效果和其他
視覺效果基礎程式庫提供高階互動修飾符
例如 Modifier.hoverable
、Modifier.focusable
和
Modifier.draggable
。
如要建構可回應懸停事件的元件,只需使用
Modifier.hoverable
,並將 MutableInteractionSource
做為參數傳遞。
將遊標懸停在元件時,會發出 HoverInteraction
,而您可以使用
變更元件的顯示方式
// This InteractionSource will emit hover interactions val interactionSource = remember { MutableInteractionSource() } Box( Modifier .size(100.dp) .hoverable(interactionSource = interactionSource), contentAlignment = Alignment.Center ) { Text("Hello!") }
如要同時將此元件設為可聚焦,您可以新增 Modifier.focusable
並傳遞
設為與參數相同的 MutableInteractionSource
。現在
已發出 HoverInteraction.Enter/Exit
和 FocusInteraction.Focus/Unfocus
相同的 MutableInteractionSource
中,您也可以自訂
這兩種互動的外觀:
// This InteractionSource will emit hover and focus interactions val interactionSource = remember { MutableInteractionSource() } Box( Modifier .size(100.dp) .hoverable(interactionSource = interactionSource) .focusable(interactionSource = interactionSource), contentAlignment = Alignment.Center ) { Text("Hello!") }
Modifier.clickable
提升了
其層級抽象化機制必須大於 hoverable
和 focusable
,
這是可點擊的元件,也是隱含的,而且可點擊的元件
也能聚焦您可以使用 Modifier.clickable
建立元件
可處理懸停、聚焦和按下互動,不必合併
API 級別您也可以將元件設為可點擊
將 hoverable
和 focusable
替換為 clickable
:
// This InteractionSource will emit hover, focus, and press interactions val interactionSource = remember { MutableInteractionSource() } Box( Modifier .size(100.dp) .clickable( onClick = {}, interactionSource = interactionSource, // Also show a ripple effect indication = ripple() ), contentAlignment = Alignment.Center ) { Text("Hello!") }
與「InteractionSource
」合作
如果您需要低層級的元件互動資訊,
針對該元件的 InteractionSource
,使用標準流程 API。
舉例來說,假設您想維護一份媒體清單
InteractionSource
的互動。這段程式碼會執行一半的工作
新的按下按鈕到清單上:
val interactionSource = remember { MutableInteractionSource() } val interactions = remember { mutableStateListOf<Interaction>() } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> { interactions.add(interaction) } is DragInteraction.Start -> { interactions.add(interaction) } } } }
除了新增互動項目以外,您也必須移除互動 結束時 (例如使用者將手指移開 元件)。這很簡單,因為最終互動一律包含 相關啟動互動的參照。這段程式碼說明移除方式 已結束的互動:
val interactionSource = remember { MutableInteractionSource() } val interactions = remember { mutableStateListOf<Interaction>() } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> { interactions.add(interaction) } is PressInteraction.Release -> { interactions.remove(interaction.press) } is PressInteraction.Cancel -> { interactions.remove(interaction.press) } is DragInteraction.Start -> { interactions.add(interaction) } is DragInteraction.Stop -> { interactions.remove(interaction.start) } is DragInteraction.Cancel -> { interactions.remove(interaction.start) } } } }
現在,如果想知道元件目前是否處於按下或拖曳狀態
你只需檢查 interactions
是否空白:
val isPressedOrDragged = interactions.isNotEmpty()
如果想知道最近一次的互動情況,請查看上一次 清單中的項目舉例來說,以下是 Compose 分享關係圖實作的方式 找出適用於最近一次互動的適當狀態疊加層:
val lastInteraction = when (interactions.lastOrNull()) { is DragInteraction.Start -> "Dragged" is PressInteraction.Press -> "Pressed" else -> "No state" }
由於所有 Interaction
都採用相同的結構,因此不會有太多
和不同類型的使用者互動
整體模式均相同
請注意,本節中的範例代表 Flow
使用 State
的互動
— 可讓您輕鬆觀測最新的值
因為讀取狀態值會自動引起重組。不過
組合是批次的前置影格。也就是說,如果狀態變更
然後在同一影格中變更回原來的位置,觀測狀態的元件就不會
相關變更
這對於互動很重要,因為互動可能會定期開始和結束
在同一畫面中例如,將前一個範例與 Button
搭配使用:
val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() Button(onClick = { /* do something */ }, interactionSource = interactionSource) { Text(if (isPressed) "Pressed!" else "Not pressed") }
如果按下按鍵開始及結束於相同畫面,則文字一律不會
「已按下!」。在大多數情況下,這並非問題,而是
這只會導致閃爍
第一,更容易注意到針對某些情況,例如顯示漣漪效果,
建議您至少以最低標準
而不是在按下按鈕時立即停止。目的地:
可以直接在集合中開始和停止動畫
lambda,而非寫入狀態。這個模式的範例在
「Build a Advanced with an anim 生效 Ball」(建構具有動畫邊框的進階 Indication
) 區段。
範例:使用自訂互動處理建構元件
如要瞭解如何使用自訂輸入回應建構元件,請參閱 修改後按鈕範例。在這種情況下,假設您想新增一個 透過變更外觀來回應按下
![動畫:使用者點選時,動態加入雜貨購物車圖示](https://developer.android.com/static/develop/ui/compose/images/handling-interactions-iconbutton.gif?authuser=0000&hl=zh-tw)
為此,請根據 Button
建構自訂可組合函式,並充分運用
要繪製圖示的額外 icon
參數 (在本例中為購物車)。個人中心
呼叫 collectIsPressedAsState()
追蹤使用者是否將遊標懸停在
按鈕;則您要新增圖示程式碼如下所示:
@Composable fun PressIconButton( onClick: () -> Unit, icon: @Composable () -> Unit, text: @Composable () -> Unit, modifier: Modifier = Modifier, interactionSource: MutableInteractionSource? = null ) { val isPressed = interactionSource?.collectIsPressedAsState()?.value ?: false Button( onClick = onClick, modifier = modifier, interactionSource = interactionSource ) { AnimatedVisibility(visible = isPressed) { if (isPressed) { Row { icon() Spacer(Modifier.size(ButtonDefaults.IconSpacing)) } } } text() } }
使用新可組合項目如下:
PressIconButton( onClick = {}, icon = { Icon(Icons.Filled.ShoppingCart, contentDescription = null) }, text = { Text("Add to cart") } )
因為這個新的 PressIconButton
是以現有 Material 為基礎
Button
的應用程式,會以所有一般方式回應使用者互動。當使用者
按下按鈕後,透明度會稍微改變,就像一般情況一樣
材質:Button
。
使用 Indication
建立及套用可重複使用的自訂特效
在先前的章節中,您已學會如何在回應中變更元件的部分內容
不同 Interaction
,例如在按下時顯示圖示。相同
這種方法可用來變更你提供給
或變更元件內顯示的內容,但這是
但僅適用於個別元件通常是應用程式或設計系統
將擁有適用於有狀態視覺效果的通用系統,
以一致的方式套用至所有元件
如要建構這種設計系統,請自訂單一元件 但如果您在其他元件中重複使用這個自訂設定, 。
- 設計系統中的每個元件都需要相同的樣板
- 容易忘記將這項效果套用至新建構的元件和自訂元件 可點擊的元件
- 想結合自訂特效和其他效果,可能並不容易
為避免發生這類問題,並在系統中輕鬆調整自訂元件的資源配置,
可以使用 Indication
。
Indication
代表可重複使用的視覺效果
應用程式或設計系統中的元件Indication
分成兩部分
部分:
IndicationNodeFactory
:建立具有Modifier.Node
例項的工廠函式 算繪元件的視覺效果。如果有比較簡單的導入作業 因為這可以是一個單例模式 (物件),並在不同元件中重複使用 整個應用程式這些執行個體可以是有狀態或無狀態。由於這些是依據個別建立 就可以從
CompositionLocal
擷取值,變更 它們在特定元件中的外觀或行為,和其他類似Modifier.Node
。Modifier.indication
: 此修飾符繪製Indication
, 元件。Modifier.clickable
和其他高階互動修飾符 直接接受指標參數,因此這些參數不僅會發出Interaction
,但也可以為其Interaction
繪製視覺效果 發出因此,如果是簡單的情況,您可以直接使用Modifier.clickable
,無需等待 需要Modifier.indication
。
將特效更換為 Indication
本節說明如何替換套用至 1 的手動縮放效果 特定按鈕,標有可重複用於多個項目的 元件。
以下程式碼會建立一個按鈕,按下時可向下縮小:
val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val scale by animateFloatAsState(targetValue = if (isPressed) 0.9f else 1f, label = "scale") Button( modifier = Modifier.scale(scale), onClick = { }, interactionSource = interactionSource ) { Text(if (isPressed) "Pressed!" else "Not pressed") }
如要將上方程式碼片段中的縮放效果轉換為 Indication
,請按照
步驟如下:
建立負責套用縮放效果的
Modifier.Node
。 附加至節點時,節點會觀察互動來源,與上一個類似 範例。唯一的差別在於,它會直接啟動動畫 而不是將傳入的互動轉換為狀態節點必須實作
DrawModifierNode
才能覆寫ContentDrawScope#draw()
,並使用相同的繪圖算繪縮放效果 就像 Compose 中的其他圖形 API 一樣從
ContentDrawScope
接收器呼叫drawContent()
將會繪圖 應套用Indication
的實際元件,因此 必須在縮放轉換內呼叫此函式請確認Indication
實作項目在某個時間點一律會呼叫drawContent()
。 否則,系統不會繪製您套用Indication
的元件。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 ) { this@draw.drawContent() } } }
建立
IndicationNodeFactory
。唯一的責任是 新節點例項。由於 參數來設定指標,工廠可以是物件:object ScaleIndication : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleNode(interactionSource) } override fun equals(other: Any?): Boolean = other === ScaleIndication override 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 fun ScaleButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, shape: Shape = CircleShape, content: @Composable RowScope.() -> Unit ) { Row( modifier = modifier .defaultMinSize(minWidth = 76.dp, minHeight = 48.dp) .clickable( enabled = enabled, indication = ScaleIndication, interactionSource = interactionSource, onClick = onClick ) .border(width = 2.dp, color = Color.Blue, shape = shape) .padding(horizontal = 16.dp, vertical = 8.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, content = content ) }
接著,您就能以下列方式使用該按鈕:
ScaleButton(onClick = {}) { Icon(Icons.Filled.ShoppingCart, "") Spacer(Modifier.padding(10.dp)) Text(text = "Add to cart!") }
![動畫按鈕,當中的雜貨購物車圖示會在按下時縮小](https://developer.android.com/static/develop/ui/compose/images/touchinput/button-custom-indication.gif?authuser=0000&hl=zh-tw)
Indication
的按鈕。建立加上動畫邊框的進階 Indication
Indication
不僅限於轉換效果,例如資源調度
元件。IndicationNodeFactory
會傳回 Modifier.Node
,因此您可以繪製
與其他繪圖 API 一樣,在內容上方或下方顯示任何形式的效果。適用對象
例如,您可以在元件周圍繪製動畫框線
按下元件時:
![點擊時帶有精美彩虹效果的按鈕](https://developer.android.com/static/develop/ui/compose/images/touchinput/rainbow-effect.gif?authuser=0000&hl=zh-tw)
Indication
繪製的動畫框線效果。這裡的 Indication
實作與上述範例非常類似:
就會建立含有一些參數的節點由於動畫邊框
設定 Indication
所用元件的形狀和邊框,以及
導入 Indication
時,必須提供形狀和框線寬度
做為參數:
data class NeonIndication(private val shape: Shape, private val borderWidth: Dp) : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return NeonNode( shape, // Double the border size for a stronger press effect borderWidth * 2, interactionSource ) } }
Modifier.Node
實作的概念也相同,即使
但繪製程式碼比較複雜和之前一樣,它觀察到 InteractionSource
附加時,啟動動畫,並實作 DrawModifierNode
以繪圖
對內容的影響:
private class NeonNode( private val shape: Shape, private val borderWidth: Dp, private val interactionSource: InteractionSource ) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedProgress = Animatable(0f) val animatedPressAlpha = Animatable(1f) var pressedAnimation: Job? = null var restingAnimation: Job? = null private suspend fun animateToPressed(pressPosition: Offset) { // Finish any existing animations, in case of a new press while we are still showing // an animation for a previous one restingAnimation?.cancel() pressedAnimation?.cancel() pressedAnimation = coroutineScope.launch { currentPressPosition = pressPosition animatedPressAlpha.snapTo(1f) animatedProgress.snapTo(0f) animatedProgress.animateTo(1f, tween(450)) } } private fun animateToResting() { restingAnimation = coroutineScope.launch { // Wait for the existing press animation to finish if it is still ongoing pressedAnimation?.join() animatedPressAlpha.animateTo(0f, tween(250)) animatedProgress.snapTo(0f) } } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { val (startPosition, endPosition) = calculateGradientStartAndEndFromPressPosition( currentPressPosition, size ) val brush = animateBrush( startPosition = startPosition, endPosition = endPosition, progress = animatedProgress.value ) val alpha = animatedPressAlpha.value drawContent() val outline = shape.createOutline(size, layoutDirection, this) // Draw overlay on top of content drawOutline( outline = outline, brush = brush, alpha = alpha * 0.1f ) // Draw border on top of overlay drawOutline( outline = outline, brush = brush, alpha = alpha, style = Stroke(width = borderWidth.toPx()) ) } /** * Calculates a gradient start / end where start is the point on the bounding rectangle of * size [size] that intercepts with the line drawn from the center to [pressPosition], * and end is the intercept on the opposite end of that line. */ private fun calculateGradientStartAndEndFromPressPosition( pressPosition: Offset, size: Size ): Pair<Offset, Offset> { // Convert to offset from the center val offset = pressPosition - size.center // y = mx + c, c is 0, so just test for x and y to see where the intercept is val gradient = offset.y / offset.x // We are starting from the center, so halve the width and height - convert the sign // to match the offset val width = (size.width / 2f) * sign(offset.x) val height = (size.height / 2f) * sign(offset.y) val x = height / gradient val y = gradient * width // Figure out which intercept lies within bounds val intercept = if (abs(y) <= abs(height)) { Offset(width, y) } else { Offset(x, height) } // Convert back to offsets from 0,0 val start = intercept + size.center val end = Offset(size.width - start.x, size.height - start.y) return start to end } private fun animateBrush( startPosition: Offset, endPosition: Offset, progress: Float ): Brush { if (progress == 0f) return TransparentBrush // This is *expensive* - we are doing a lot of allocations on each animation frame. To // recreate a similar effect in a performant way, it would be better to create one large // gradient and translate it on each frame, instead of creating a whole new gradient // and shader. The current approach will be janky! val colorStops = buildList { when { progress < 1 / 6f -> { val adjustedProgress = progress * 6f add(0f to Blue) add(adjustedProgress to Color.Transparent) } progress < 2 / 6f -> { val adjustedProgress = (progress - 1 / 6f) * 6f add(0f to Purple) add(adjustedProgress * MaxBlueStop to Blue) add(adjustedProgress to Blue) add(1f to Color.Transparent) } progress < 3 / 6f -> { val adjustedProgress = (progress - 2 / 6f) * 6f add(0f to Pink) add(adjustedProgress * MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } progress < 4 / 6f -> { val adjustedProgress = (progress - 3 / 6f) * 6f add(0f to Orange) add(adjustedProgress * MaxPinkStop to Pink) add(MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } progress < 5 / 6f -> { val adjustedProgress = (progress - 4 / 6f) * 6f add(0f to Yellow) add(adjustedProgress * MaxOrangeStop to Orange) add(MaxPinkStop to Pink) add(MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } else -> { val adjustedProgress = (progress - 5 / 6f) * 6f add(0f to Yellow) add(adjustedProgress * MaxYellowStop to Yellow) add(MaxOrangeStop to Orange) add(MaxPinkStop to Pink) add(MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } } } return linearGradient( colorStops = colorStops.toTypedArray(), start = startPosition, end = endPosition ) } companion object { val TransparentBrush = SolidColor(Color.Transparent) val Blue = Color(0xFF30C0D8) val Purple = Color(0xFF7848A8) val Pink = Color(0xFFF03078) val Orange = Color(0xFFF07800) val Yellow = Color(0xFFF0D800) const val MaxYellowStop = 0.16f const val MaxOrangeStop = 0.33f const val MaxPinkStop = 0.5f const val MaxPurpleStop = 0.67f const val MaxBlueStop = 0.83f } }
主要差異在於
animateToResting()
函式建立動畫,因此即使按下
此時按下動畫就會繼續播放處理中作業
在 animateToPressed
開始時多次快速按下 (如果按壓按鈕)
會在現有的按下或靜息動畫播放期間發生
取消後,按下動畫就會從頭開始播放。支援多種
兩個並行效果 (例如透過漣漪效果,新的分享關係會
除了其他漣漪效果外,您也可以追蹤清單中的動畫,不必使用
取消現有動畫並啟動新動畫。
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 瞭解手勢
- 適用於 Jetpack Compose 的 Kotlin
- Material Design 元件和版面配置