dimsuz
12/02/2021, 6:17 PMhasFocus = false
immediately after one of the children receives hasFocus = true
Zach Klippenstein (he/him) [MOD]
12/02/2021, 6:32 PMRalston Da Silva
12/02/2021, 7:04 PM@Composable
fun HasFocusSample(){
Column(
Modifier.onFocusChanged { println("hasFocus = ${it.hasFocus}") }
) {
BasicTextField(value= "TextField1", onValueChange = {})
BasicTextField(value= "TextField2", onValueChange = {})
}
}
Initially
hasFocus = false
After clicking TextField1
hasFocus = true
After clicking TextField2
hasFocus = false
hasFocus = true
I think you are wondering why hasFocus was set to false. This is because in this sample, the common focus parent for TextField1 and TextField2 are above the Column. So when TextField1 loses focus, there is no focus in the hierarchy, when TextField2 gains focus, focus is restored to the hierarchy.
However, if you make the Column focusable, then hasFocus will stay true, as focus doesn't leave the sub-hierarchy:
@Composable
fun HasFocusSample(){
Column(
Modifier
.onFocusChanged { println("hasFocus = ${it.hasFocus}") }
.focusable()
) {
BasicTextField(value= "TextField1", onValueChange = {})
BasicTextField(value= "TextField2", onValueChange = {})
}
}
Now the initial state is
hasFocus = false
After clicking TextField1
hasFocus = true
After clicking TextField2
// no change
So there are two ways you can proceed:
Option 1. Don't react to the onFocusChanged event, instead use hoisted state in the logic where you need to know if Column has focus
@Composable
fun HasFocusSample(){
var columnHasFocus by remember { mutableStateOf(false) }
Column(Modifier.onFocusChanged { columnHasFocus = it.hasFocus}) {
BasicTextField(value= "TextField1", onValueChange = {})
BasicTextField(value= "TextField2", onValueChange = {})
}
}
Option 2: Keep focus within this hierarchy by making the Column focusable, so that you don't get the intermediate hasFocus= false
@Composable
fun HasFocusSample(){
Column(
Modifier
.onFocusChanged { it.hasFocus }
.focusable()
) {
BasicTextField(value= "TextField1", onValueChange = {})
BasicTextField(value= "TextField2", onValueChange = {})
}
}
If you don't want to actually make the Column focusable, but just want it to be a focus parent, you can use
Modifier
.focusProperties { canFocus = false }
.focusable()
Zach Klippenstein (he/him) [MOD]
12/02/2021, 7:16 PMonFocusChanged
merely received focus changed events directly from its children. LayoutNodes know nothing about focus, so there’s no concept of the Column
having focused “children” when it doesn’t have the focusable
modifier, because as far as focus is concerned Column
doesn’t exist. So putting onFocusChanged
on the column is basically just like putting it on both the children directly.Ralston Da Silva
12/02/2021, 8:22 PMdimsuz
12/02/2021, 11:02 PMboolean
state, because I need something like enum FocusState { Field1, Field2, Field3, None }
, so I have a setup where each individual field sets the state to the corresponding enum case while onFocusChange
from parent sets None
in case hasFocus
is false
.
I didn't set Column
to focusable()
, so I've used Option 1 from your example, but somehow when clicking in 1st text field I observed this sequence Field1
, None
, None
, None
(several of `None`s, may be due to recompositions). So for me it's not behaving as you described, hasFocus
somehow turns into false
in parent's listener. Maybe it's my setup, I'll need to come up with a minimal example to check.
It's 1.0.5
version of compose, didn't try on newer one.