I use DelegateAdapters to display different sections in the parent RecyclerView. In this code I am adding two sections (AuthorTracksDelegateItem and NewReleasesDelegateItem) that have titles and a list of tracks that will then be displayed in nested horizontal lists. Also, I will observe LiveData so that I can add a third section (PlayerDelegateItem) when another track appears and then everything is as usual
fun setAdapter(recyclerView: RecyclerView){
//Add sections
val testData = listOf(
AuthorTracksDelegateItem(title = "Beatles", tracks = homeViewModel.getTracks("Beatles")),
NewReleasesDelegateItem(title = "New releases", albums = homeViewModel.getNewReleases()),
)
compositeAdapter.setItems(testData)
//Observe and add new section
homeViewModel.getTracks("Imagine dragons").observe(viewLifecycleOwner) { tracks ->
if(tracks.isNotEmpty()){
compositeAdapter.addToEnd(PlayerDelegateItem(title = "Last play", content = tracks.first()))
}
}
//other
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = compositeAdapter
val padding = resources.getDimensionPixelSize(R.dimen.section_margin)
recyclerView.addItemDecoration(MarginItemDecoration(padding, padding*10, padding))
}
After that, already in ViewHolders, I also create new nested lists, everything is as usual.
inner class NewReleasesViewHolder(itemView: View):
DelegateViewHolder(itemView)
{
private val title = itemView.findViewById<TextView>(R.id.section_title)
private val recyclerView = itemView.findViewById<RecyclerView>(R.id.rv_horizontal_tracks)
private val adapter = NewReleasesAdapter()
override fun bind(item: IDelegateAdapterItem) {
title.text = item.id() as String
(item.content() as LiveData<List<Album>>).observeForever { albums ->
adapter.setItems(albums)
}
setupRecycleView()
}
private fun setupRecycleView(){
val layout = LinearLayoutManager(itemView.context)
layout.orientation = LinearLayoutManager.HORIZONTAL
recyclerView.adapter = adapter
recyclerView.layoutManager = layout
recyclerView.addItemDecoration(
MarginItemDecoration(
startSpace = itemView.resources.getDimensionPixelSize(R.dimen.content_horizontal_margin),
endSpace = itemView.resources.getDimensionPixelSize(R.dimen.content_horizontal_margin),
betweenSpace = itemView.resources.getDimensionPixelSize(R.dimen.items_margin),
isHorizontal = true
)
)
}
}
override fun createViewHolder(binding: View): RecyclerView.ViewHolder {
return NewReleasesViewHolder(binding)
}
override fun getLayoutId(): Int {
return R.layout.section_nested_list
}
inner class TrackViewHolder(itemView: View): DelegateViewHolder(itemView){
val title = itemView.findViewById<TextView>(R.id.section_title)
val recyclerView = itemView.findViewById<RecyclerView>(R.id.rv_horizontal_tracks)
val adapter = LargeTracksAdapter()
override fun bind(item: IDelegateAdapterItem) {
title.text = item.id() as String
(item.content() as LiveData<List<Track>>).observeForever { tracks ->
adapter.setItems(tracks)
}
setupRecycleView()
}
fun setupRecycleView(){
val layout = LinearLayoutManager(itemView.context)
layout.orientation = LinearLayoutManager.HORIZONTAL
recyclerView.layoutManager = layout
recyclerView.adapter = adapter
recyclerView.addItemDecoration(MarginItemDecoration(
startSpace = itemView.resources.getDimensionPixelSize(R.dimen.content_horizontal_margin),
endSpace = itemView.resources.getDimensionPixelSize(R.dimen.content_horizontal_margin),
betweenSpace = itemView.resources.getDimensionPixelSize(R.dimen.items_margin),
isHorizontal = true
))
recyclerView.invalidateItemDecorations()
}
}
In order to add padding between elements I use ItemDecoration
class MarginItemDecoration(
private val startSpace: Int = 0,
private val endSpace: Int = 0,
private var betweenSpace: Int = 0,
private val isHorizontal: Boolean = false): RecyclerView.ItemDecoration() {
init {
betweenSpace /= 2
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val size = parent.adapter?.itemCount ?: 0
val itemPosition = parent.getChildAdapterPosition(view)
if (isHorizontal) {
outRect.left = getStartSpace(itemPosition)
outRect.right = getEndSpace(itemPosition, size)
} else {
outRect.top = getStartSpace(itemPosition)
outRect.bottom = getEndSpace(itemPosition, size)
}
}
private fun getStartSpace(
itemPosition: Int
): Int = when (itemPosition) {
0 -> startSpace
else -> betweenSpace
}
private fun getEndSpace(
itemPosition: Int,
size: Int,
): Int = when (itemPosition) {
size-1 -> endSpace
else -> betweenSpace
}
}
Although I used the same and fixed values from dimensions.xml, the indentation shows different, for some reason, in the second section it is twice as large screenshot
I noticed that the indents break when a new section is added to the parent RecyclerView, I tried setting the delay to 5 seconds. Everything was fine, but after adding the indents they broke again. I tried adding invalidateItemDecorations() but that didn't help either. Maybe I’m somewhere in the wrong place or didn’t use them correctly, since a new section is added to the parent RecyclerView, and the indents are broken in the nested ones. I tried using different layouts and different IDs, but nothing helped. I also tried to add several sections, just duplicated them, but something completely strange happened
//Add sections
val testData = listOf(
AuthorTracksDelegateItem(title = "Beatles", tracks = homeViewModel.getTracks("Beatles")),
NewReleasesDelegateItem(title = "New releases", albums = homeViewModel.getNewReleases()),
AuthorTracksDelegateItem(title = "Linkin Park", tracks = homeViewModel.getTracks("Linkin Park")),
AuthorTracksDelegateItem(title = "21 pilots", tracks = homeViewModel.getTracks("21 pilots")),
)
I expected that in each subsequent section the space between elements would double, but this is what happened: The section that was added via observe (even if it was zero) was displayed normally, but there were no nested lists there
I really don’t understand why the hell he behaves like this, why sections 1, 2 and 4, which are of the same class, are displayed completely differently source code: https://github.com/Ikrom27/music-club-classic
Perhaps, when adding a new element, all nested lists are redrawn again and a second itemDecoration
is added to them. Therefore, each time you need to either remove all decorations or before adding, check that the list has no other decorations
if (recyclerView.itemDecorationCount == 0) {
recyclerView.addItemDecoration(MarginItemDecoration(
startSpace = itemView.resources.getDimensionPixelSize(R.dimen.content_horizontal_margin),
endSpace = itemView.resources.getDimensionPixelSize(R.dimen.content_horizontal_margin),
betweenSpace = itemView.resources.getDimensionPixelSize(R.dimen.items_margin),
isHorizontal = true
))
}