No Good Example About Recyclerview and Staggeredgridlayoutmanager in Android Docs

Jumbled tiles in itemTouchHelperCallback in StaggeredGridLayoutManager

The problem:

Returning the item position from getItemViewType() of the RecyclerView adapter; while you only have one item view type.

Short Answer:

You shouldn't override getItemViewType() to keep it to the default, or you can make it return 0 to indicate that there is a single view type for the adapter.

Sample Image

Long Answer:

When the adapter is first attached to the RecyclerView, The RecyclerView's LayoutManager needs to know the layout type of each item position; and to do that it calls getItemViewType(position) for each single position in the adapter; so if the adapter list has 10 items, then it's called 10 times; each time it is called, then it returns the type of this particular item.

When getItemViewType(position) is not overriden the returned value for all of the items is 0; which indicates that all the items have the same type.

By logging that while returning 0:

2021-01-13 11:05:25.941 : getItemViewType:  Pos = 0 Return: 0
2021-01-13 11:05:25.950 : onBindViewHolder: Pos: 0
2021-01-13 11:05:25.954 : getItemViewType: Pos = 1 Return: 0
2021-01-13 11:05:25.959 : onBindViewHolder: Pos: 1
2021-01-13 11:05:25.962 : getItemViewType: Pos = 2 Return: 0
2021-01-13 11:05:25.970 : onBindViewHolder: Pos: 2
2021-01-13 11:05:25.974 : getItemViewType: Pos = 3 Return: 0
2021-01-13 11:05:25.979 : onBindViewHolder: Pos: 3
2021-01-13 11:05:25.982 : getItemViewType: Pos = 4 Return: 0
2021-01-13 11:05:25.988 : onBindViewHolder: Pos: 4
2021-01-13 11:05:26.000 : getItemViewType: Pos = 0 Return: 0
2021-01-13 11:05:26.001 : getItemViewType: Pos = 1 Return: 0
2021-01-13 11:05:26.001 : getItemViewType: Pos = 2 Return: 0
2021-01-13 11:05:26.001 : getItemViewType: Pos = 3 Return: 0
2021-01-13 11:05:26.001 : getItemViewType: Pos = 4 Return: 0

But as you instead return position from getItemViewType(position); then you have as many types of items as the number of positions you have (i.e. the size of the list).

The log in this case:

2021-01-13 11:11:30.123 : getItemViewType:  Pos = 0 Return: 0
2021-01-13 11:11:30.145 : onBindViewHolder: Pos: 0
2021-01-13 11:11:30.149 : getItemViewType: Pos = 1 Return: 1
2021-01-13 11:11:30.160 : onBindViewHolder: Pos: 1
2021-01-13 11:11:30.164 : getItemViewType: Pos = 2 Return: 2
2021-01-13 11:11:30.172 : onBindViewHolder: Pos: 2
2021-01-13 11:11:30.175 : getItemViewType: Pos = 3 Return: 3
2021-01-13 11:11:30.179 : onBindViewHolder: Pos: 3
2021-01-13 11:11:30.183 : getItemViewType: Pos = 4 Return: 4
2021-01-13 11:11:30.190 : onBindViewHolder: Pos: 4
2021-01-13 11:11:30.199 : getItemViewType: Pos = 0 Return: 0
2021-01-13 11:11:30.199 : getItemViewType: Pos = 1 Return: 1
2021-01-13 11:11:30.200 : getItemViewType: Pos = 2 Return: 2
2021-01-13 11:11:30.200 : getItemViewType: Pos = 3 Return: 3
2021-01-13 11:11:30.200 : getItemViewType: Pos = 4 Return: 4

Now the RecyclerView is in the steady state (i.e. populated with all the items), and we are going to drag one item and drop it on another.

Now we'll Replace Note no.1 with 3 (i.e. replacing position 4 with position 2 in the adapter)

The log when getItemViewType() returns 0

2021-01-13 11:14:26.620 : getItemViewType:  Pos = 0 Return: 0
2021-01-13 11:14:26.620 : getItemViewType: Pos = 1 Return: 0
2021-01-13 11:14:26.620 : getItemViewType: Pos = 2 Return: 0
2021-01-13 11:14:26.621 : getItemViewType: Pos = 3 Return: 0
2021-01-13 11:14:26.621 : getItemViewType: Pos = 4 Return: 0

Here the getItemViewType is called only once for each position and that is because you have only a single view type. And there is no calls to onBindViewHolder as we just called notifyItemMoved so that the RecyclerView only calls gets cached/recycled versions as all the view types are the same in this case.

The log when getItemViewType() returns position

2021-01-13 11:12:13.659 : getItemViewType:  Pos = 0 Return: 0
2021-01-13 11:12:13.660 : getItemViewType: Pos = 1 Return: 1
2021-01-13 11:12:13.660 : getItemViewType: Pos = 2 Return: 2
2021-01-13 11:12:13.660 : getItemViewType: Pos = 2 Return: 2
2021-01-13 11:12:13.665 : onBindViewHolder: Pos: 2
2021-01-13 11:12:13.667 : getItemViewType: Pos = 3 Return: 3
2021-01-13 11:12:13.668 : getItemViewType: Pos = 3 Return: 3
2021-01-13 11:12:13.673 : onBindViewHolder: Pos: 3
2021-01-13 11:12:13.675 : getItemViewType: Pos = 4 Return: 4
2021-01-13 11:12:13.675 : getItemViewType: Pos = 4 Return: 4
2021-01-13 11:12:13.681 : onBindViewHolder: Pos: 4

Here the getItemViewType is called multiple times as each item has its unique view type, and it needs to recalculate that View type, and also redraw (or recalculate the layout) of the item using onBindViewHolder for the from (item 2) & to (item 4) items and the items in-between (item 3).

Making these multiple view types make you unable to complete the Drag (probably due to new layout recalculation), and also let the intermediate Views unable to shift between the dragged and dropped-on items.

Android - Staggered Grid View

This is how it's supposed to work:

The StaggeredGridLayoutManager will show a staggered grid if and only if images have different sizes: it will try to fill the gaps using a strategy that you can define in your code. There is also a talk by Dave Smith explaining this.

If the images are all of the same size, the grid will look like a regular one.

How to Make StageredRecyclerView?

The resources on StaggeredGridLayout recyclerview implementation are scarce.

Lucky for you here is the example thread on stackoverflow, a solution for simple implementation of staggered grid.

No good example about RecyclerView and StaggeredGridLayoutManager in Android Docs

How to build somewhat complicated recyclerview layout without ugly codes?

Ahhh of course there's already a library that does that: https://github.com/Arasthel/SpannedGridLayoutManager

Still trying it out but so far it seems to do exactly what I need to do!

How to reflow items in a RecyclerView with the StaggeredGridLayoutManager

Maybe I'm wrong (there's no good documentation about this out there yet), but I think you cannot achieve that with this layout manager. The staggered layout manager lets you place your views within spans, and if you place one that occupies the all of the spans, it will calculate the position for the next view based on the current one (without taking a look at the previous one).

In other words, if you place a view that occupies all of the spans, the layout manager will find the shortest span for the next view, and because you occupied all of them with the same view, the first span is the candidate.

I hope this helps you!



Related Topics



Leave a reply



Submit