Reputation: 124
I already have a list adapter that works properly. But I want to divide the object in the list into sections according to the date they were created. Something like this:
I found something called "sectioned recycler view" but couldn't find any documentation on that. I read all the related questions, but they all are either outdated or use a third-party library. What's the native way of implementing this feature?
Upvotes: 0
Views: 1202
Reputation: 96
Use nested recycler view for this instead. You can check the example here. Best solution for this scenario so far.
Upvotes: 1
Reputation: 19544
There are a couple of approaches you could use. First the easy one:
GONE
visibility by defaultonBindViewHolder
, decide whether the header should be VISIBLE
or GONE
The logic there depends on what you want, but it could be as simple as
val visible = position == 0 || items[position].date != items[position - 1].date
Basically you just need to work out what the condition is that would cause an item to be in a different "group" than the previous item, and then if it's met, show the header over that item.
The approach MarkL is talking about is more complex, but it's also more robust - by having separate Item
and Header
elements, you can treat them differently, and even do stuff like having the header collapse/show its children, select them all etc. You can do that with the other approach, but it requires more code since you're not really treating things as groups, it's more of a trick when it comes to displaying stuff.
Basically, ignoring the how for now, you need a list of headers and items. A sealed class is a good way to represent that:
sealed class ListElement {
data class Header(val date: Date) : ListElement()
data class Item(val itemData: YourItem) : ListElement()
}
I've made Item
a wrapper class that holds your actual data object inside, since that's probably coming from elsewhere and you can't define it as part of this sealed class hierarchy - so sticking it inside one of the subclasses like this allows you to do that.
So now you can have a List<ListElement>
containing Header
s and Item
s in display order. Since you've mentioned creating the ViewHolder
s in a comment I won't explain all that, but basically when you're getting the item type for a position, you just need to check is Header
or is Item
and then handle it from there.
As for creating that list, there are lots of ways to do it - you could use groupBy
to generate a Map
of dates to lists of items, map each of those entries to a list of Header, Item, Item...
, and flatten the whole thing into a single list:
items.map { Item(it) }
.groupBy { it.itemData.date }
.entries
.flatMap { (date, items) -> listOf(Header(date)) + items }
The advantage with a map like this is it's an actual grouped structure, so you can keep it around to generate flat lists for display - e.g. hiding a group's contents by only including the header in the list.
Or you could just build the list yourself, similar to the logic I mentioned in the first example - if the date has changed from the previous item, insert a Header
first:
val list = mutableListOf<ListElement>().apply {
for (item in items) {
// add a header if the date changed - this handles the first header
// in an empty list too (where lastOrNull returns null, so the date is null)
val previousItemDate = (lastOrNull() as? Item)?.itemData?.date
if (previousItemDate != item.date) add(Header(item.date))
add(Item(item))
}
}
Or you could use fold
. Don't forget to sort stuff!
Upvotes: 3
Reputation: 21
You could create 2 types of view holders:
And then create a list of objects which contain something like this: listToBind = (header, data, data, header, data, data)
For your case, where header & info is the same object, you can do something like this:
take your object you receive from backend (example) YourObject(val header: String, val info:InfoObject)
create 2 display objects, both inheriting from a type that your adapter accepts (say - AdapterDisplayEntity) HeaderDisplayEntity(val header: String): AdapterDisplayEntity InfoDisplayEntity(val info: InfoObject): AdapterDisplayEntity
now you can use your list that contains these items and submit to your adapter.
Upvotes: 2
Reputation: 5407
If you are using Jetpack Compose you can use the stickyHeader() as documented in the documentation
Upvotes: 0