官方文檔鏈接:
https://developer.android.google.cn/develop/ui/compose/touch-input/focus?hl=zh-cn
1、更改焦點遍歷順序
1.1、替換一維遍歷順序
(1)創建焦點引用對象:
/// 創建4個引用對象(二選一)
// 語法1:
val (first, second, third, fourth) = remember { FocusRequester.createRefs() }
// 語法2:數組
val focusRequesters = remember { List(4){ FocusRequester.createRefs() } }
(2)使用 focusRequester 修飾符將它們分別與 可組合項
Column {Row {// 注意:TextButton 具有默認焦點(可聚焦)// 如果此處是沒有默認聚焦的元素,比如 Box,則需要在Modifer最后面(所有焦點函數之后)// 注意是最后面加上 .focusable()TextButton({}, Modifier.focusRequester(first/*focusRequesters[0]*/)/* .focusable()*/) { Text("First field") }TextButton({}, Modifier.focusRequester(third/*focusRequesters[1]*/)) { Text("Third field") }}Row {TextButton({}, Modifier.focusRequester(second/*focusRequesters[2]*/)) { Text("Second field") }TextButton({}, Modifier.focusRequester(fourth/*focusRequesters[3]*/)) { Text("Fourth field") }}
}
(3)使用 focusProperties 修飾符指定自定義遍歷順序
因為有些布局或組件默認會帶有遍歷默認順序,比如 Column 的孩子的默認遍歷順序是從上到下。
但是有時候我們需要改變這種順序:
// 一維:它的下一個是誰、上一個是誰
Modifier.focusRequester(first).focusProperties { next = second }..../* .focusable()*/...// 二維:上下左右
Modifier.focusRequester(fourth).focusProperties {down = thirdright = second}
2 更改焦點行為
2.1 焦點小組
LazyVerticalGrid(columns = GridCells.Fixed(4)) {item(span = { GridItemSpan(maxLineSpan) }) {// 將其設置為一個焦點組:Row(modifier = Modifier.focusGroup()) {FilterChipA()FilterChipB()FilterChipC()}}items(chocolates) {SweetsCard(sweets = it)}
}
2.2 使可組合項可聚焦
var color by remember { mutableStateOf(Green) }
Box(Modifier.background(color).onFocusChanged { color = if (it.isFocused) Blue else Green }// 注意要放在焦點相關函數的最后面,.focusable()
) {Text("Focusable 1")
}
2.3 使可組合項不可聚焦
var checked by remember { mutableStateOf(false) }Switch(checked = checked,onCheckedChange = { checked = it },// Prevent component from being focusedmodifier = Modifier// 使原本可以聚焦的,變得不可聚焦....focusProperties { canFocus = false }
)
2.4 使用 FocusRequester 手動請求焦點
上面說了可以手動添加焦點引用對象。添加這個請求對象之后,可以使用焦點請求對象手動請求焦點…
// 焦點請求對象
val focusRequester = remember { FocusRequester() }
var text by remember { mutableStateOf("") }TextField(value = text,onValueChange = { text = it },// 為這個元素添加上面的焦點請求對象(覆蓋默認的焦點請求對象)modifier = Modifier.focusRequester(focusRequester)
)// 在添加焦點請求對象之后,就可以手動請求:
Button(onClick = {// 手動請求(在.focusRequester(focusRequester)之后)focusRequester.requestFocus() }) {Text("Request focus on TextField")
}
2.5 捕獲和釋放 焦點
調用 captureFocus() 方法,并且之后必須要用 freeFocus() 方法將其釋放
// 經常用在 TextField 之類的組件
// 比如下面的示例中,首先 TextField 已經獲取焦點,
// 但是我還想讓它在輸入長度達到 3 之后突出視覺效果,可以嘗試捕獲焦點…
val textField = FocusRequester()TextField(value = text,onValueChange = {text = itif (it.length > 3) {textField.captureFocus()} else {textField.freeFocus()}},modifier = Modifier.focusRequester(textField)
)
2.6 焦點修飾符的優先級
Modifier.focusProperties { right = Default }.focusProperties { right = item1 }.focusProperties { right = item2 }// 放在最后....focusable()
注意:焦點相關修飾函數順序由決定性作用,不同的順序會帶來不同的效果:
// 作用失效案例1:
Box(Modifier.focusable() // 讓焦點函數生效.focusRequester(Default)// 后聲明請求器 → 無法關聯.onFocusChanged {}// 當焦點發生變化的時候,回調// 上面這個案例會導致下面兩個焦點修飾符函數不生效。// 因為 focusable() 的作用的讓(前面已經)配置生效。// 所以先生效,再配置的邏輯順序是不符合要求的
)
// 作用失效案例2:
Box(Modifier.onFocusChanged {}// 先監聽焦點變化(無效),應該先綁定焦點.focusRequester(Default).focusable()
)// 正確順序:
Box(Modifier.focusRequester(Default) // 先聲明請求器.onFocusChanged {} // 可放在此處(但可能不推薦).focusable() // 后聲明可聚焦 → 請求器生效
)
2.7 進入或退出時重定向焦點
什么是進入:
一般就是 Enter 鍵觸發的行為
什么是退出:退出可組合區域,比如說焦點離開Colum1,進入Column2。
離開Column1就是退出行為。
比如在第一列離開,如果想要將焦點放置在第二列,可以:
val otherComposable = remember { FocusRequester() }Modifier.focusProperties {// 離開第一列的時候,我們根據離開的方向指定下一個焦點應該到達何處exit = { focusDirection ->when (focusDirection) {Right -> Cancel// 離開且按右方向鍵,就取消焦點Down -> otherComposable// 離開且按下鍵,就移動到第二列else -> Default// 其他方向使用默認}}
}
2.8 更改焦點推進方向
val focusManager = LocalFocusManager.current
var text by remember { mutableStateOf("") }TextField(value = text,onValueChange = { text = it },modifier = Modifier.onPreviewKeyEvent {when {// 檢測到tab按鍵(并釋放抬起),焦點移至焦點中的下一個元素 列表KeyEventType.KeyUp == it.type && Key.Tab == it.key -> {// 移動焦點到默認的下一個焦點元素focusManager.moveFocus(FocusDirection.Next)true}else -> false}}
)
3、回應焦點
3.1、添加視覺效果(比如顏色)
var color by remember { mutableStateOf(Color.White) }
Card(modifier = Modifier.onFocusChanged {color = if (it.isFocused) Red else White}.border(5.dp, color)
) {}
3.2、高級視覺提示
(1)首先,創建一個 IndicationInstance,以在界面中直觀地繪制所需提示:
private class MyHighlightIndicationNode(private val interactionSource: InteractionSource) :Modifier.Node(), DrawModifierNode {private var isFocused = falseoverride fun onAttach() {coroutineScope.launch {var focusCount = 0interactionSource.interactions.collect { interaction ->when (interaction) {is FocusInteraction.Focus -> focusCount++is FocusInteraction.Unfocus -> focusCount--}val focused = focusCount > 0if (isFocused != focused) {isFocused = focusedinvalidateDraw()}}}}override fun ContentDrawScope.draw() {drawContent()if (isFocused) {drawRect(size = size, color = Color.White, alpha = 0.2f)}}
}
(2)接下來,創建一個 Indication 并記住聚焦狀態:
object MyHighlightIndication : IndicationNodeFactory {override fun create(interactionSource: InteractionSource): DelegatableNode {return MyHighlightIndicationNode(interactionSource)}override fun hashCode(): Int = -1override fun equals(other: Any?) = other === this
}
(3)通過 indication() 修飾符將 Indication 和 InteractionSource 添加到界面中:
var interactionSource = remember { MutableInteractionSource() }Card(modifier = Modifier.clickable(interactionSource = interactionSource,indication = MyHighlightIndication,enabled = true,onClick = { })
) {Text("hello")
}
3.3、焦點狀態
// 焦點一般有這三種狀態,可以直接在焦點回調里面獲取:
Modifier.onFocusChanged {val isFocused = it.isFocused// 只檢查當前元素val hasFocus = it.hasFocus// 不僅檢查當前,還檢查孩子們val isCaptured= it.isCaptured//每當獲得焦點時,isCaptured 都會返回 true// 一般出現這種清空都是當 TextField 包含不正確的數據時,就會嘗試聚焦 其他元素不會清除焦點。
}