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"
)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With