有時候,您必須覆寫畫面上元素的預設焦點行為。例如,您可能想要將可組合項分組、避免聚焦特定可組合項、明確要求焦點位於某個可組合項、擷取或釋放焦點,或是在進入或離開時重新導向焦點。本節說明如何在預設值不符合需求時變更焦點行為。
透過焦點群組提供連貫的導覽體驗
有時 Jetpack Compose 不會立即猜測分頁導覽的下一個項目,尤其是當分頁和清單等複雜的父項 Composables
上線時。
雖然焦點搜尋通常會遵循 Composables
的宣告順序,但在某些情況下不會這麼做,例如階層中的其中一個 Composables
是無法完全顯示的水平捲動項目。如以下範例所示。
Jetpack Compose 可能會決定將下一個項目聚焦在最靠近畫面的開頭處 (如下所示),而不是繼續前往您預期的單向導覽路徑:
在這個例子中,開發人員明顯不想把重點從「Chocolates」(巧克力) 分頁移到下方第一張圖片,然後再返回「Pastries」(糕點) 分頁。反之,他們希望聚焦於分頁繼續停留到最後一個分頁,然後將焦點放在內部內容:
當系統必須依序取得一組可組合項 (例如上例中的分頁列) 時,您必須將 Composable
納入含有 focusGroup()
修飾符的父項中:
LazyVerticalGrid(columns = GridCells.Fixed(4)) { item(span = { GridItemSpan(maxLineSpan) }) { Row(modifier = Modifier.focusGroup()) { FilterChipA() FilterChipB() FilterChipC() } } items(chocolates) { SweetsCard(sweets = it) } }
雙向導覽會尋找指定方向最接近的可組合項,如果其他群組中的元素距離目前群組中無法完全顯示的項目距離更近,導覽功能就會挑選最接近的項目。如要避免這種行為,可以套用 focusGroup()
修飾符。
FocusGroup
可讓整個群組在焦點上看起來像單一實體,但群組本身不會取得焦點,而會改為聚焦距離最近的子項。如此一來,導覽就知道在離開群組前,會前往無法看見的項目。
在此情況下,即使使用者已完全能看到 SweetsCards
,且部分 FilterChip
可能隱藏,系統仍會將焦點放在 SweetsCard
項目之前的三個 FilterChip
例項。這是因為 focusGroup
修飾符會指示焦點管理員調整聚焦項目的順序,讓導覽更容易且與使用者介面更一致。
如果沒有 focusGroup
修飾符,則不顯示 FilterChipC
時,焦點導覽功能最後會回到該修飾符。不過,新增這類修飾詞不僅可偵測出來,還能像使用者預期一樣在 FilterChipB
後立即取得焦點。
將可組合項設為可聚焦
部分可組合項的設計可聚焦,例如按鈕或附加 clickable
修飾符的可組合項。如果您想特別將可聚焦行為新增至可組合項,請使用 focusable
修飾符:
var color by remember { mutableStateOf(Green) } Box( Modifier .background(color) .onFocusChanged { color = if (it.isFocused) Blue else Green } .focusable() ) { Text("Focusable 1") }
將可組合項變成無法聚焦
在某些情況下,部分元素不應參與焦點。在這種極少數的情況下,您可以使用 canFocus property
從無法聚焦的 Composable
中排除。
var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it }, // Prevent component from being focused modifier = Modifier .focusProperties { canFocus = false } )
使用 FocusRequester
要求鍵盤焦點
在某些情況下,您可能會想要明確要求焦點,以回應使用者互動。舉例來說,您可以詢問使用者是否要重新在表單中填入資料,並詢問使用者「是」時,想重新聚焦該表單的第一個欄位。
首先,請將 FocusRequester
物件與您要將鍵盤焦點移至哪個可組合項建立關聯。在以下程式碼片段中,系統會設定名為 Modifier.focusRequester
的修飾符,將 FocusRequester
物件與 TextField
建立關聯:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) )
您可以呼叫 FocusRequester 的 requestFocus
方法,以便傳送實際焦點要求。您應在 Composable
內容以外的地方叫用這個方法 (否則,系統會在每次重組時重新執行此方法)。下列程式碼片段示範如何要求系統在使用者點選按鈕時移動鍵盤焦點:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) ) Button(onClick = { focusRequester.requestFocus() }) { Text("Request focus on TextField") }
擷取並放開焦點
您可以運用重點引導使用者提供應用程式執行工作所需的適當資料,例如取得有效的電子郵件地址或電話號碼。雖然錯誤狀態會讓使用者瞭解實際情況,但您可能需要含有錯誤資訊的欄位,直到問題修正為止。
如要擷取焦點,您可以叫用 captureFocus()
方法,然後改用 freeFocus()
方法發布該方法,如以下範例所示:
val textField = FocusRequester() TextField( value = text, onValueChange = { text = it if (it.length > 3) { textField.captureFocus() } else { textField.freeFocus() } }, modifier = Modifier.focusRequester(textField) )
優先使用焦點修飾符
Modifiers
可以視為只有一個子項的元素,因此在將其排入佇列時,左側 (或頂端) 的每個 Modifier
都會納入右側 (或下方) 後面的 Modifier
。這表示第二個 Modifier
包含在第一個函式中,因此在宣告兩個 focusProperties
時,只有最頂層的一個有效,因為下列項目包含在最頂層。
如要進一步釐清概念,請參閱下列程式碼:
Modifier .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
在這種情況下,系統不會使用指出 item2
做為正確焦點的 focusProperties
,因為上述內容已包含在上述標記中;因此,系統會使用 item1
。
使用這個方法時,父項也可以使用 FocusRequester.Default
將行為重設為預設值:
Modifier .focusProperties { right = Default } .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
父項不一定要屬於同一個修飾符鏈結。父項可組合項可以覆寫子項可組合項的焦點屬性。舉例來說,請考慮使用此 FancyButton
,導致按鈕無法聚焦:
@Composable fun FancyButton(modifier: Modifier = Modifier) { Row(modifier.focusProperties { canFocus = false }) { Text("Click me") Button(onClick = { }) { Text("OK") } } }
使用者只要將 canFocus
設為 true
,即可讓此按鈕再次成為焦點:
FancyButton(Modifier.focusProperties { canFocus = true })
和每個 Modifier
一樣,焦點相關項目的行為取決於您宣告的順序。舉例來說,如下所示的程式碼可讓 Box
成為可聚焦,但 FocusRequester
會在可聚焦後宣告,因此不會與此可聚焦項目建立關聯。
Box( Modifier .focusable() .focusRequester(Default) .onFocusChanged {} )
請務必記得,focusRequester
與階層中下方第一個可聚焦的項目相關聯,因此這個 focusRequester
會指向第一個可聚焦的子項。如果沒有可用的資料,則不會指向任何項目。
不過,由於 Box
可聚焦 (感謝 focusable()
修飾符),因此您可以使用雙向導覽功能前往該修飾符。
再舉一個例子,由於 onFocusChanged()
修飾符參照 focusable()
或 focusTarget()
修飾符之後的第一個可聚焦元素,因此可以使用下列指令。
Box( Modifier .onFocusChanged {} .focusRequester(Default) .focusable() ) |
Box( Modifier .focusRequester(Default) .onFocusChanged {} .focusable() ) |
進入或離開時將焦點重新導向
有時您需要提供非常具體的導覽類型,例如下方動畫中的導覽:
深入探討如何建立這項功能之前,請務必先瞭解焦點搜尋的預設行為。在未經任何修改的情況下,當焦點的搜尋到達 Clickable 3
項目時,在 D-Pad 上按下 DOWN
(或對等的方向鍵) 會將焦點移至 Column
下方顯示的內容,離開群組並忽略右側的群組。如果沒有可聚焦的項目,則焦點不會移動,而是會留在 Clickable 3
上。
如要修改此行為並提供預期的導覽,您可以利用 focusProperties
修飾符,管理焦點搜尋進入或離開 Composable
時會發生什麼事:
val otherComposable = remember { FocusRequester() } Modifier.focusProperties { exit = { focusDirection -> when (focusDirection) { Right -> Cancel Down -> otherComposable else -> Default } } }
每當焦點進入或離開階層的某個部分時,可以將焦點導向特定 Composable
。例如,當 UI 有兩個資料欄,而您想確保系統每次處理第一個資料欄時,請將焦點切換到第二個:
在這張 GIF 中,當焦點到達 Column
1 中的 Clickable 3 Composable
時,下一個聚焦項目就是另一個 Column
中的 Clickable 4
。透過結合 focusDirection
與 focusProperties
修飾符中的 enter
和 exit
值,即可達成此行為。兩者都需要一個 lambda,該參數會將焦點的來源方向做為參數,並傳回 FocusRequester
。此 lambda 的行為有三種不同:傳回 FocusRequester.Cancel
會讓焦點停止繼續,但 FocusRequester.Default
不會變更其行為。而改為提供附加至其他 Composable
的 FocusRequester
,可將焦點跳至該特定 Composable
。
變更焦點前進方向
如要將焦點移至下一個項目或朝著精確方向移動,您可以利用 onPreviewKey
修飾符並直接使用 LocalFocusManager
來透過 moveFocus
修飾符引導焦點。
以下範例顯示焦點機制的預設行為:偵測到 tab
鍵時,焦點會前進至焦點清單中的下一個元素。雖然這並非需要設定的項目,但您必須瞭解系統內部運作,才能變更預設行為。
val focusManager = LocalFocusManager.current var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.onPreviewKeyEvent { when { KeyEventType.KeyUp == it.type && Key.Tab == it.key -> { focusManager.moveFocus(FocusDirection.Next) true } else -> false } } )
在此範例中,focusManager.moveFocus()
函式會將焦點移到指定項目,或函式參數中隱含的方向。
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 做出回應
- Compose 的焦點
- 變更焦點遍歷順序