I'm trying to use Jetpack Compose in my existing AndroidTV App. I need to make a button with microphone icon which will change its color if it's focused. Like this^
Unfocused
Focused
Here's my ComposeView
<androidx.compose.ui.platform.ComposeView
android:id="@+id/micBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
And here the code in my fragment
binding.micBtn.setContent {
var buttonResId by remember { mutableStateOf(R.drawable.speech_recognition_button_unfocused) }
IconButton(
modifier = Modifier
.size(60.dp)
.onFocusChanged {
buttonResId = if (it.isFocused) {
R.drawable.speech_recognition_button_focused
} else {
R.drawable.speech_recognition_button_unfocused
}
},
onClick = onClick,
) {
Icon(
painter = painterResource(id = buttonResId),
contentDescription = null,
tint = Color.Unspecified,
)
}
}
Looks good, right?
The problem is when I try to focus on this button focus first goes to AndroidComposeView
item (according to my GlobalFocusListener
).
And only my second action (click, D-Pad navigation) makes my content focused.
So, for some reason internal AndroidComposeView
steals focus from my Content
Is there any way to prevent this behaviour? I need to focus only on my content, not AndroidComposeView
wrapper.
This issue has been addressed in aosp/2813125 and the following hack shouldn't be required anymore. Just update to the latest version of Compose UI (1.7.x).
This is a known issue where moving focus from outside of a ComposeView to inside the ComposeView needs 2 inputs from the user instead of just 1: b/292432034
You can create an extension function which can transfer the focus to the child using just 1 input from the user.
fun ComposeView.setFocusableContent(content: @Composable () -> Unit) {
isFocusable = true
isFocusableInTouchMode = true
val focusRequester = FocusRequester()
onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
if (hasFocus) focusRequester.requestFocus()
}
setContent {
Box(modifier = Modifier.focusRequester(focusRequester).focusGroup()) {
content.invoke()
}
}
}
Usage is pretty straight forward. Instead of using setContent
, you can now use setFocusableContent
.
binding.micBtn.setFocusableContent {
// ...
}
To react to focus changes in your component, you can make use of IconButton from androidx.tv.material3
library. By default, it changes color when the button is focused and it is easy to change the colors for different states (focused, pressed, etc.) using the colors
parameter.
Usage:
androidx.tv.material3.IconButton(onClick = { }) {
Icon(
Icons.Filled.Mic,
contentDescription = "Mic"
)
}