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.
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
Android: How Do Bluetooth Uuids Work
Listselector Applies to the Entire List
Determining If an Android Device Is Rooted Programmatically
Android Edittext Onchange Listener
How Does Evaluatejavascript Work
Android Update Textview in Thread and Runnable
How to Implement a Contentobserver for Call Logs
Android: Get Height of a View Before It's Drawn
Hiding Title in a Fullscreen Mode
Extract Code Country from Phone Number [Libphonenumber]
List All the Files from All the Folder in a Single List
Getting a Photo from a Contact
How to Run Countdowntimer in a Service in Android
Switch Keyboard Profile Programmatically
How to Display a Route Between Two Geocoords in Google Maps
Adb Server Is Out of Date. Killing
Webview Showing Err_Cleartext_Not_Permitted Although Site Is Https