RecyclerView item inserted with item updated is skipping inserted animation - android

I have a basic RecyclerView setup on a chat-like app and I have hit an issue with the item animations.
The project is making use of Room with Paging 3 and DiffUtils for the RecyclerView adapter, so this is all automated, but the core of the problem can be simplified to this:
When I send a new message, that message is added to the RecyclerView
here the adapter is triggering notifyItemInserted or notifyItemRangeInserted which causes the entire message list to shift up softly and the new message fades in after
I scroll the list to the bottom so the new added item becomes visible
When I receive a read status from the server I update the status of that message
here the adapter is triggering notifyItemChanged or notifyItemRangeChanged which has no default animations on its own, it just updates the item with the new information
All of this is working well on its own, but the problem is when I receive a status update from the server faster than the insert animation has a chance to finish. When that happens the notifyItemChanged or notifyItemRangeChanged kicks in and skips the animation initiated by notifyItemInserted or notifyItemRangeInserted. The list till shifts upwards, but the fade in no longer happens, instead the item is instantly made visible all the while the list is still shifting up, overlaying the item previously occupying that last position causing an ugly visual experience.
I can kinda "cheat" by delaying the step in 2. to engage after the animation is supposedly over, but then it introduces another visual issue if the user sends multiple messages quickly or receives them in the same fashion or in certain cases it just does not show any animation because the new item is loaded outside of the list and only scrolled after the animation time is elapsed, so this is not a solution.
first
second
In this example there are 2 recyclerviews set up with the same adapter slightly changed to make it easier to compare the issue in the same action.
The left recyclerview is not doing any update when an item is inserted, but it is the behavior I expect to display even if I update the item during the item insertion animation.
On the right recyclerview is the actual problem, as you can see new items are showing in full over the old ones before they have a chance to move out of the way.
The first example recording has scroll to bottom with no delay after the item is inserted, the second example has a delay that matches the insertion animation duration.
Reminder: this is just a manual example, the real application in my case is being done automatically via the integration I mentioned above, I am not the one in control of when the notifyItem* calls are made at any point.
How can I make sure the insert animation does not get interrupted even if I am updating the item data in the middle of the animation?
EDIT: I already searched for a solution in the questions posted before, but none are related to this one nor do the similar ones provide a solution to my problem.

Related

Android RecyclerView items empty when being swiped after switching apps

First of all, I have looked at similar questions (for example, this one: Android RecyclerView ItemTouchHelper revert swipe and restore view holder). This already helped a great deal, until I - more or less, accidentally - noticed my current (and hopefully, final) issue with this screen of mine.
Let's start with the setup:
I have a fragment with a RecyclerView filled with some CardView items (it's a little fancier, but that's what is important right now). I also created an ItemTouchHelper with the implementation of SimpleCallback (nothing in onMove()) to make swiping the items (right) possible. For the record: I am using API 27 right now.
So far, so good.
What I want to achieve:
I want to be able to swipe the items to be notified through the onSwiped() method of my SimpleCallback implementation. Also, I do NOT want the items to disappear, be removed, or otherwise taken out of my list of items in the RecyclerView. I just want to swipe them and have them return to their original position afterward (and yes, I know that it is sort of assumed that swiped items get removed). I am using the notifyItemChanged() method of my adapter in the onSwiped() method (also tried using notifyDataSetChanged()).
The problem:
Funnily enough, that works (mostly thanks to the aforementioned question) - until I hit that "app switch" button (don't know if there's actually one official name for it) and send the app to the background. Once I put it in the foreground again and start swiping, the items will not (visually) return. They are still on the list, and if I scroll or click the "app switch" button again, they will be displayed properly again (but won't return on swiping). Same if I navigate back one screen and come back to the list.
That makes me think something happens when I send the app to the background and recover it. Something different than navigating to that screen (in which case everything works as intended) - which I thought would more or less produce the same results. Any ideas what I might be overlooking here?
After some testing, I finally found the source of the issue:
I had both the RecyclerView and its Adapter initialised through onStart() of the Fragment and not onViewCreated(). After changing that, I got the proper results I wanted.
Lesson learned: Set your RecyclerView's Adapter as early as possible, unless you want to deal with sometimes strange issues.

Freeze a ListView during sequential updates

I have a ListView with an ArrayAdapter to back it. Updates to the list typically involve multiple item add/remove operations, followed by a sort operation. Often, the update just removes one item and adds an almost identical one, which eventually ends up in the same place as the item I removed.
How can I prevent the list view from “twitching” during these operations? Ideally, I would like to freeze all redraw operations, then add, remove and sort items, and only then “unfreeze” the UI, allowing it to be redrawn. Any way to achieve that?
ArrayAdapter#setNotifyOnChange() seems like a candidate for this. Use as follows:
myAdapter.setNotifyOnChange(false);
// add, remove and sort items here
myAdapter.notifyDataSetChanged();
Ensure nothing else in the update code calls ArrayAdapter#notifyDataSetChanged() (as doing so will trigger a redraw), and updates will be delayed until the transaction is complete.
EDIT: after trying this, there is still the occasional twitch, but that may also be caused by the fact that I scroll the selected list view item (back) into view after each update.

Recyclerview single item choice. No multi touch

This is driving me crazy. Something that is implemented by default in ListView is missing from RecyclerView.
I want to have the exact behavior of a listview which is set to single choice mode. I tried the solution from this question but it doesn't fully solve the problem. When I touch the list with 2-3 fingers etc it may only highlight one row indeed, but if I lift my fingers rapidly everything gets triggered even tho only one row is highlighted each time. (I can also hear the sound effect from my phone that gets repeated 3 times very fast)
Basically I want to disable multi touch events from the phone so that the list is forced to select only 1 item each time no matter how many fingers the user uses
To disable multi touch in recyclerview, you can use
android:splitMotionEvents="false" in your recyclerview tag in layout file.
By that attribute, you will not receive multi touch in recyclerview.
The problem is that i start a new thread every time user clicks on a row so if i try to multi touch the recycler's items, even in your sample app more than one threads start simultaneously and unexpected results happen.
That has nothing to do with multi-touch. You would get the same effect with one finger, rapidly tapping on different rows. It may be easier for you to reproduce the effect via multi-touch, but there is no guarantee that the user will never tap a second row while the thread for your first row is still outstanding.
Your problem is with your implementation. Either:
Tapping on multiple rows in rapid succession is fine, and you need to fix the "unexpected results", such as serializing the threads, having a single thread and a LinkedBlockingQueue, or synchronizing access to shared data, or
Tapping on multiple rows in rapid succession is not fine, in which case you would need to track whether a thread is outstanding when the user taps on a row, then decide what to do (discard the event? queue the work to be done, so it is performed after the current thread is done? something else?).
Another possibility would be to get rid of the "fork a thread on a list row click" entirely, and require a more positive step (e.g., click some Done button) before you do whatever work it is that you are trying to do.

Mvvmcross Android ListView AutoScroll Bottom When Item Added

I am having trouble finding the event to bind in the ListView adapter to scroll the listview to the bottom when a new item is added. When I initially populate the list its scrolled to the bottom which is correct but on an subsequent adds to the lists via a polling service it doesn't seem to stay on the bottom and I am forced to manually scroll. I am just wondering what events I need to hook into in order to set the index or automatically scroll to the bottom.
Okay I just noticed that when I add another item it pushes up 1 so it seems 1 item is always hidden when I add a new one, not quite sure how to fix this.
After doing more research I realised the error was due to me updating the collection on a different thread. Needed to be on the UIThread.
https://github.com/MvvmCross/MvvmCross-Tutorials/blob/master/Working%20With%20Collections/Collections.Droid/Views/PolymorphicListItemTypesView.cs

Android ListView, where new items push the old ones 'down'

My app has a ListView where data is added from the top, automatically (through setData on a Adapter). When data is set, usually most of the data remains the same, and only a few items are added at the top. Typically tens of items exist, and 1-2 items are added at the top every ~10 seconds.
Right now the user experience sucks. It completely re-draws the list with the new data. If a user was reading an item just before data got updated, he will lose his old position and will need to scroll to find it again.
What I would like to see as a more human compatible transition / animation, where the old data gets pushed down, and new items are added at the top. Or - new data pushes old data 'down' - and I can see this animation.
(Something like a 'ticker' - only vertical, and items are on a listview.)
I went through ListViewAnimations, and JazzyListView. They are very nice, but they don't support my requirement.
I hate re-inventing the wheels, so after coming up empty I wanted to make sure there really isn't anything out there.
If a user was reading an item just before data got updated, he will
lose his old position and will need to scroll to find it again.
Before updating the list view remember the position and restore it.
Some thing like this:
private int position;
private void save(){
position = myListView.getFirstVisiblePosition();
}
private void restore(){
myListView.setSelection(position);
}
You should check this, its from Android Developer page:
http://developer.android.com/training/animation/layout.html#activity
You might be able to getFirstVisiblePosition before calling setData, then setSelection after. That will still jump slightly (snapping to the current first visible item), unfortunately I believe you need to subclass ListView to access the pixel scroll value (getScrollY is for the view itself, not for scrolling inside it).
Maybe consider letting the user decide when to reload? For instance, display a list header "3 new items, swipe down to refresh" or some such.

Categories

Resources