去年夏天,我寫了一篇關於使用 Compose 進行專注管理的部落格文章。從那時起,我就一直保留著這篇文章的草稿,但直到現在才最終定稿。這篇部落格文章的標題是:[It's All About (Accessibility) Focus And Compose][4]。
所以,在這篇文章中,我們將討論焦點指示器以及如何使用 Compose 讓它們更容易存取。但首先,讓我們來談談焦點指示器的基本概念。
焦點指示器顧名思義,用於顯示鍵盤焦點在使用者介面中的目前位置。它們必須可見,以便鍵盤和鍵盤模擬裝置使用者能夠輕鬆地在應用程式中進行導航。
需要注意的是,我這裡所說的焦點指示器並非指螢幕閱讀器(例如 TalkBack)的焦點。螢幕閱讀器的焦點是由系統層級處理的。
Web 內容無障礙指南(儘管名稱如此,但它是無障礙立法背後的標準,也用於應用程式)對無障礙焦點指示器有一些要求。根據 [SC 2.4.13 焦點外觀][1],焦點指示器需要滿足以下條件之一:
使用者代理(Android 系統)的預設樣式
至少 2 個像素厚,且聚焦狀態和未聚焦狀態下相同像素的對比度至少為 3:1。
Android 的預設焦點指示器是漣漪效果,但它不太顯眼。從技術上講,這樣勉強夠用,但如果你想讓應用更易於存取,就需要提高焦點指示器的可見性。接下來,我們將討論如何使用 Compose 建立更顯眼(從而更易於存取)的焦點指示器。
建立焦點指示器有多種方法。例如,您可以根據焦點狀態新增邊框,如 Appt.org 在其程式碼片段中建議的那樣:[Jetpack Compose 中的輔助功能焦點指示器][2]。但如果您需要更複雜的功能,則需要使用 Indication API。
[指示 API][3] 搭配DrawModifierNode可以繪製複雜的焦點指示器。在本篇部落格文章中,我們將在目前焦點專案(首先是一個按鈕)下方繪製一條簡單的線條:

然後是一個換行行:

我們本質上是在建立一個修飾符,它可以用於任何互動式元件。我們希望盡可能將邏輯封裝在這個修飾符和其他元件中,以便盡可能簡化在 Compose 程式碼中的使用。
為此,我們需要三樣東西:
修飾符(我們稱之為focusIndication )
IndicationNodeFactory用於建立指示( FocusIndication )
最後,使用DrawModifierNode實際繪製焦點指示器( FocusNode )。
讓我們從清單的末尾開始。
為了建立實際的指示,我們需要定義一個類,該類接受交互源和顏色作為參數。它繼承了Modifier.Node()和DrawModifierNode ,並重寫了兩個方法: onAttach和ContentDrawScope.draw() 。
class FocusNode(
val interactionSource: InteractionSource,
val color: Color
) : Modifier.Node(), DrawModifierNode {
override fun onAttach() {
…
}
override fun ContentDrawScope.draw() {
…
}
}
onAttach函數處理交互。讓我們加入一個內部變數來儲存元件的焦點狀態,並將其儲存在onAttach函數中:
private var isFocused by mutableStateOf(false)
override fun onAttach() {
coroutineScope.launch {
interactionSource.interactions.collect { interaction ->
when (interaction) {
is FocusInteraction.Focus -> isFocused = true
is FocusInteraction.Unfocus -> isFocused = false
}
}
}
}
這裡,我們使用建構函式中傳入的interactionSource來收集元件的互動資訊。我們現在只專注於焦點交互,但interactionSource.interactions也包含例如按下交互,因此如果您想建立自訂的按下樣式,也可以在這裡處理它們。
然後,在ContentDrawScope.draw中,我們繪製焦點指示器:
override fun ContentDrawScope.draw() {
drawContent()
if (isFocused) {
drawRect(
color = color,
topLeft = Offset(
x = 0f,
y = size.height - 8f
),
size = Size(
width = size.width,
height = 12f
)
)
}
}
我們首先使用drawContent繪製內容,然後,如果isFocused為真,則在元件下方繪製一個 12 像素高的矩形。我們希望稍微偏移它的位置,使其正確顯示。至於顏色,我們使用建構函數中傳入的color 。
下一步是使用這個FocusNode 。我們將建立一個繼承自IndicationNodeFactory資料類別:
private data class FocusIndication(
val color: Color
) : IndicationNodeFactory {
override fun create(
interactionSource: InteractionSource
): Modifier.Node {
return FocusNode(interactionSource, color)
}
}
在這個範例中,我們重寫了create函數並傳回我們建立的FocusNode實例。最後,我們定義了一個接受interactionSource的修飾符,並將其命名為focusIndication :
@Composable
fun Modifier.focusIndication(
interactionSource: MutableInteractionSource
): Modifier {
val focusColor = MaterialTheme.colorScheme.surfaceTint
val focusIndication = remember {
FocusIndication(
color = focusColor
)
}
return indication(interactionSource, focusIndication)
}
首先,我們有focusColor變數,在本例中,它取自主題顏色中的surfaceTint 。如文章開頭所述,它與非聚焦狀態下相同像素的顏色對比至少應為 3:1。這意味著最好分別設定淺色和深色主題顏色,因為很難找到一種顏色能夠同時滿足兩種模式的要求。
之後,我們記住先前建立的FocusIndication ,並將focusColor作為color參數傳遞。最後,我們傳回一個包含interactionSource和focusIndication indication修飾符。
有時,我們希望在觸控模式下隱藏焦點指示器,因為它在使用者與互動元件互動時過於頻繁地顯示,或者出於某種原因需要手動管理焦點。 InputModeManager 可以幫助InputModeManager實現這一點。
首先,對於FocusNode ,我們再增加一個它擴充的接口,即CompositionLocalConsumerModifierNode :
class FocusNode(
…
) : Modifier.Node(),
DrawModifierNode,
CompositionLocalConsumerModifierNode {
…
}
這樣,我們就可以使用LocalInputModeManager的值,並讀取其輸入模式:
override fun ContentDrawScope.draw() {
drawContent()
val inputMode = currentValueOf(LocalInputModeManager).inputMode
if (isFocused && inputMode == InputMode.Keyboard) {
…
}
}
我們使用currentValueOf(LocalInputModeManager).inputMode讀取值,檢查模式是否為InputMode.Keyboard ,然後才繪製焦點指示。
好了,現在我們已經準備好了對焦指示器。我們該如何使用它?
具體用法取決於元件。對於具有內建互動的元件,例如按鈕或文字字段,使用起來很簡單。我們定義一個互動來源,將其傳遞給元件的interactionSource參數,然後使用相同的interactionSource呼叫focusIndication修飾符:
val buttonInteractionSource = remember { MutableInteractionSource() }
Button(
modifier = Modifier.focusIndication(buttonInteractionSource),
interactionSource = buttonInteractionSource,
onClick = {}
) {
Text("A Button")
}
對於使用諸如clickable 、 toggleable或selectable等修飾符進行互動的自訂元件,新增自訂焦點指示器需要更多步驟。
在以下 Switch 行的範例中,為了清楚起見,我省略了嚴格超出焦點指示範圍的部分:
val switchInteractionSource = remember { MutableInteractionSource() }
Row(
modifier = Modifier
.focusIndication(switchInteractionSource)
.toggleable(
…
indication = ripple(),
interactionSource = switchInteractionSource
),
) {
Text(
"A switch"
)
Switch(
…
interactionSource = switchInteractionSource,
)
}
我們定義一個互動來源,並將其命名為switchInteractionSource 。然後,我們將該交互源傳遞給一個toggleable修飾符,該修飾符用於使整行可切換。我們也傳入了指示函數ripple() -否則,觸摸時就不會出現漣漪效果。
最後,我們也將互動來源傳遞給Switch ,以便如果使用者點擊開關元件,漣漪效果就會在整行中可見。
在這篇文章中,我們討論瞭如何為互動式 Compose 元件新增自訂焦點指示器。我們研究了Indication API 及其使用方法,並建立了一個自訂修飾符來封裝邏輯,以便更輕鬆地使用。您可以在[此 Github gist][5]中找到完整的程式碼。
我有一個後續文章的想法:建立不同類型的關注指標,以表明你實際上可以對它們進行一些創意設計。
[一切皆關乎(輔助功能)專注與構圖][4]
[SC 2.4.13 焦點外觀][1]
[Jetpack Compose 中的輔助功能焦點指示器][2]
[適應症 API][3]
[完整程式碼在此Github gist中][5]
原文出處:https://dev.to/eevajonnapanula/more-accessible-focus-indicators-with-compose-1ca4