Compose interoperability
The dpadrecyclerview-compose
module contains the following:
DpadComposeFocusViewHolder
: ViewHolder that exposes a function to render a Composable and sends the focus directly to Composables.
DpadComposeViewHolder
: ViewHolder that exposes a function to render a Composable but keeps the focus state in the View system
RecyclerViewCompositionStrategy.DisposeOnRecycled
: a custom ViewCompositionStrategy
that only disposes compositions when ViewHolders are recycled
Compose ViewHolder
Receive focus inside Composables
Use DpadComposeFocusViewHolder
to let your Composables receive the focus state.
class ComposeItemAdapter (
private val onItemClick : ( Int ) -> Unit
) : ListAdapter < Int , DpadComposeFocusViewHolder < Int >> ( Item . DIFF_CALLBACK ) {
override fun onCreateViewHolder (
parent : ViewGroup ,
viewType : Int
): DpadComposeFocusViewHolder < Int > {
return DpadComposeFocusViewHolder ( parent ) { item ->
ItemComposable (
item = item ,
onClick = {
onItemClick ( item )
}
)
}
}
override fun onBindViewHolder (
holder : DpadComposeFocusViewHolder < Int > ,
position : Int
) {
holder . setItemState ( getItem ( position ))
}
}
Then use the standard focus APIs to react to focus changes:
@Composable
fun ItemComposable (
item : Int ,
onClick : () -> Unit ,
modifier : Modifier = Modifier ,
) {
var isFocused by remember { mutableStateOf ( false ) }
val backgroundColor = if ( isFocused ) Color . White else Color . Black
val textColor = if ( isFocused ) Color . Black else Color . White
Box (
modifier = modifier
. background ( backgroundColor )
. onFocusChanged { focusState ->
isFocused = focusState . hasFocus
}
. focusable ()
. dpadClickable {
onClick ()
},
contentAlignment = Alignment . Center ,
) {
Text (
text = item . toString (),
color = textColor ,
fontSize = 35. sp
)
}
}
Keep focus inside the view system
If you want to keep the focus inside the View system, use DpadComposeViewHolder
instead:
class ComposeItemAdapter (
private val onItemClick : ( Int ) -> Unit
) : ListAdapter < Int , DpadComposeViewHolder < Int >> ( Item . DIFF_CALLBACK ) {
override fun onCreateViewHolder (
parent : ViewGroup ,
viewType : Int
): DpadComposeViewHolder < Int > {
return DpadComposeViewHolder (
parent ,
onClick = onItemClick
) { item , isFocused ->
ItemComposable ( item , isFocused )
}
}
override fun onBindViewHolder (
holder : DpadComposeViewHolder < Int > ,
position : Int
) {
holder . setItemState ( getItem ( position ))
}
}
In this case, you receive the focus state as an input that you can pass to your Composables:
@Composable
fun ItemComposable ( item : Int , isFocused : Boolean ) {
val backgroundColor = if ( isFocused ) Color . White else Color . Black
val textColor = if ( isFocused ) Color . Black else Color . White
Box (
modifier = Modifier . background ( backgroundColor ),
contentAlignment = Alignment . Center ,
) {
Text (
text = item . toString (),
color = textColor ,
fontSize = 35. sp
)
}
}
Handle clicks with sound
Use Modifier.dpadClickable
instead of Modifier.clickable
because of this issue:
/b/268268856
If you plan to use compose animations, check the performance during fast scrolling and consider throttling key events using the APIs explained here
Consider using dpadRecyclerView.setLayoutWhileScrollingEnabled(false)
to discard layout requests during scroll events.
This will skip unnecessary layout requests triggered by some compose animations.
Check the sample on Github for more examples that include simple animations.