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.
Related
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.
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.
i am using a recycler view with a list adapter. When submitting a list with a completely or almost completely different items the animation doesn't look good. Another problem is that i have no control on the scroll position as scroll to position is not processed as expected. It makes perfect sense to reset the scroll position to zero when the data is changed.
I ended up setting up a new adapter when switching all the data.
Would be helpful to have a better solution.
I would expect to have a method where you notify the adapter/recycler view that you want to switch all the data without trying to applying the diff mechanism and animating the changes.
I would like to be able to animate the appearance of the new list but avoid trying to animate what actually changed.
I'm using Recyclerview to show a list. I want to delete some items like IOS. In my listview template I have added a button to delete item which is invisible by default. In my activity I have another button attached at bottom (Not part of listview) and on tap of this button I want to make all delete buttons of listview visible.
My Question is how can I get reference to all delete buttons of listview in activity and is it the right way to do this?
Thanks
Assuming you have ViewHolders set up, you already have references to all the buttons in your list. All you have to do is to make them visible for every item in the list with a simple loop.
In case you haven't implemented ViewHolders I suggest you check out the documentation and take a look at some simple tutorials on how to use them.
On a side note. If I understood correctly you're making a bottom tab for your app and since you referenced iOS I gotta say this; Remember that Android and iOS are two unique operating systems with their own ways of handling things. Check out Googles pure Android documentation.
In your question title you say RecyclerView, but in your text you say ListView. The solution is similar either way, but it's best to be perfectly clear what you're doing.
In either case, there are at least two different solutions.
First, you could use a boolean flag to determine if all the the item buttons should be showing or not. You check this flag at the time the item view is inflated or created and toggle the button accordingly. If the boolean flag is ever changed, the easiest thing to do is tell the RecyclerView/ListView that the underlying data has changed and to redraw all the views. Call notifyDatasetChanged on the adapter.
The other thing you can do at the time the item buttons should change is iterate all the visible item views, find the button, and change its visibility. With RecyclerView, you can do this, and with ListView you can do this.
I am developing an application that needs to show calendar agenda, much like the agenda in the native calendar. I have a list view showing different events (Note: in the context of the question events is entity of my system). I want to provide a 'Today' button on this screen. When the user clicks on this button the events are supposed to be scrolled until the first event of the current's day schedule is on top of the screen. The problem occurs when I have only a few events scheduled from today on - so few that they do not fill a whole screen. Then the list view just scrolls until the last event in the calendar is on the bottom. This usually means that the desired effect of having the first today's event on top is not achieved.
Any suggestions how this can be done? I have thought of adding some blank elements at the end, but this seems ugly workaround, and furthermore it will require special device-specific calculations that will tell me how many elements to insert.
Edit:
Adding some code as requested in comment
Actually I am not sure this code will surprise anyone, but:
public void onTodayClicked(View target) {
// calculate the indexOf. It works and is not related to the question
if (indexOf >= 0) {
ListView list = (ListView) findViewById(R.id.events_list_view);
list.setSelection(indexOf);
}
}
I am not sure the layout definition is important to aid the answering of the question, but if you think so I can add it too.
You can achieve this by two ways:
call smoothScrollToPositionFromTop method
call setSelectionFromTop method
Using the smoothScroll method is better, because it actually does the transition smoothly - that means it really scrolls to it.
The only downside is that it's only available after API level 11.
The setSelectionFromTop method works since API level 1, but instead of smoothly scrolling, it jumps to the row.
If you don't need to position to the top of the screen, only to view the row, you can also use smoothScrollToPosition, which is an API level 8 call.
If you give these methods the position, which is the FIRST in the list, they will work well. (From your description I think you probably calculate the last position, but I can't be sure).