Background:
I'm developing a tournament management software and I want to show the current contenders classification on a screen that is feed by an android device.
The number of contenders exceeds the vertical size of the screen, so I would like to show it using a nice vertical animate smoth scroll from bottom to top that would be endless (when the list shows the last element it would start showing the first as it would be a loop.
Approach:
After some research (sorry I don't have the all the references) I come up with the following implementation:
I'm using a recyclerview with an adapter on a fragment. In order to automatically scroll it up I have added a timer that would scroll the recycleview on fixed itervals:
CountUpTimer recyclerViewAutoScroll = new CountUpTimer(1) {
public void onTick(long millis) {
recyclerView.scrollBy(0, mSpeed * 2);
if (!recyclerView.canScrollVertically(1)) {
recyclerView.scrollToPosition(0);
}
}
};
This timer is being created and launched on a global layout listener:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
layoutListener = () -> {
recyclerView.post(() -> recyclerViewAutoScroll.start());
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
}
The CountUpTimer class is just an "infinite" timer implemented using an android CountDowntimer.
With this piece of code I can scroll up at regular times, the problem is that is not smoth at all. There are two parameters to play with: the timer tick time, and the amount of vertical scroll that is made each time the timer "ticks". The only way to make it "smooth" is to reducing so much the scroll movement that it ended being very very very slow for the user.
Just for completeness, to achieve the endless scroll efect I added an scroll listener to the recycler view:
recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(5) {
#Override
public void onLoadMore(int currentPage) {
Timber.d("new Page " + currentPage);
recyclerView.post(() ->
{
itemAdapter.addModel(currentClassificationList);
if (currentPage > 2)
itemAdapter.removeModelRange(0, currentClassificationList.size());
});
}
What I do there is just add twice the classificationList to have enough space on the bottom to show the top of the list after the last element, and I keep adding the list to the bottom and removing from the top every time the recyclerview is about to reach the end of the list.
So my question is: How can I implement a really smoth scroll animation of the recyclerview? As I said this is being shown WITHOUT any user interaction, just on a screen where the classification is continuously being shown and scrolled.
UPDATE
After one comment suggestion I substituted the count timer by a TimeAnimator class this way:
timeAnimator.setTimeListener(new TimeAnimator.TimeListener() {
#Override
public void onTimeUpdate(TimeAnimator timeAnimator, long l, long l1) {
Timber.d("elapsed: " + l1);
recyclerView.scrollBy(0, (int)(l1/ mSpeed));
if (!recyclerView.canScrollVertically(1)) {
recyclerView.scrollToPosition(0);
}
}
});
Also I removed the post calls as they are not needed.
Now the smoothness has improved, but is not perfect, it still hessitate a bit when a new item is about to appear from the bottom.
I'm looking now to the itemadapter to improve any calculation made there and any image loading (I added Picasso for image loading also) But still it is not perfect, they are some flickering there.
Related
I am building an app using Android Studio, and I cannot figure out how to approach this issue I am facing.
I have a recyclerview that takes up about two-thirds of the lower half of the screen. Above it are three other elements. I need those elements to disappear when the recyclerview gets scrolled, and I need the recyclerview to scale up to fill the entire screen. I have found ways to do this, but they all cause the items inside of the recyclerview to scale up as well, distorting them.
Use this implementation:-
recyclerview.addOnScrollListener(new HidingScrollListener()
{
#Override
public void onHide()
{
tabss.animate().translationY(tabss.getHeight())
.setInterpolator(new AccelerateInterpolator(2)).start();
filtershow.animate().translationY(-filtershow.getHeight())
.setInterpolator(new AccelerateInterpolator(1));
}
#Override
public void onShow()
{
tabss.animate().translationY(0).setInterpolator(new
DecelerateInterpolator(2)).start();
filtershow.animate().translationY(0).setInterpolator
(new DecelerateInterpolator(1)).start();
}
});
I want to make a RecyclerView that scrolls infinitely while also being able to scroll to an item programmatically.
At the moment I've made the RecyclerView loop infinitely using this hacky method: https://stackoverflow.com/a/31254146/7443375
i.e. Overriding my adapter
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
Getting position of item in my adapter like so:
int positionInList = position % fragmentList.size();
And then initializing my RecyclerView's scroll position like so:
recyclerView.getLayoutManager().scrollToPosition(Integer.MAX_VALUE / 2);
However, in my Fragment that has the RecyclerView, I want to be able to scroll to a specific item in my list (i.e. item 3 out of a list of 10 items). When I call
recyclerView.getLayoutManager().scrollToPosition(2);
The list goes into an infinite scroll. I can't figure out how to go to the specific item itself.
Furthermore, how can I make sure that the specific item is centered in the screen? I am using LinearSnapHelper to snap the items in the center of the screen as I scroll, but LinearSnapHelper does not seem to work when setting positions programmatically.
Try this way..
recyclerView.post(new Runnable() {
#Override
public void run() {
new CountDownTimer(Integer.MAX_VALUE, 20 ) {
public void onTick(long millis) {
recyclerView.scrollBy(0, 20);
}
public void onFinish() {
}
}.start();
}
});
I basically have to smooth scroll a listview and update a row at the same time.
I do it with a simple approach for now:
mListViewWeeks.post(new Runnable() {
#Override
public void run() {
// update the row concerned
updateItemAtPosition(rowIndex);
int duration = 200;
mListView.smoothScrollToPositionFromTop(rowIndex, 0, duration);
}
});
with the updateItemAtPosition() function:
private void updateItemAtPosition(int position) {
int visiblePosition = mListView.getFirstVisiblePosition();
View view = mListView.getChildAt(position - visiblePosition);
mListView.getAdapter().getView(position, view, mListView);
}
It's working well at a reasonable scroll speed, but when going faster (calling the first block above at a high rate) it can get a bit laggy. Is there anything that I can do to improve updating a row while scrolling smoothly?
You should'nt acces the iew dirrectly like this. Instead you should update your model object displayed by the list and call notifyDataSetChanged() in your adapter.
Well, I'm really late in the game. It looks like one of the reason why RecyclerView was introduced. I'm gonna try to use this component from now on.
A combination of layoutManager.scrollToPosition(position);
and adapter.notifyItemChanged(position); does the job. Everything runs smoothly!!
I have a ViewPager with a couple of RecyclerViews as pages. I would like to implement functionality where RecyclerViews which are on other pages move by certain amount after user starts scrolling pages.
#Override
public void onPageScrolled(int position, float offset, int offsetPx) {
RecyclerView view1 = getPage(position - 1);
RecyclerView view2 = getPage(position + 1);
if(scrollNeeded()) {
view1.scrollBy(0, 200);
view2.scrollBy(0, 200);
}
}
The problem which I have is that everything works fine if I scroll slowly through my ViewPager but if I scroll crazy fast, some RecyclerViews don't get scrolled. I guess I somehow need to synchronize this method.
Any idea how to solve this problem? User shouldn't see that scroll.
ViewPager keeps +1 page left and right preloaded. Which means
in very beginning - current page and the next one
at the very end - last page and the previous one
anywhere else - current, previous and next
When user swipes really fast through pages, there is a real case where the page (your RecyclerView instance and its adapter) are still preparing, so they miss the scrollBy() call.
You can solve this in different ways.
Easiest is increasing the number of cached off screen pages (e.g. 3) by calling viewPager.setOffscreenPageLimit(3) - for more ViewPager.setOffScreenPageLimit(int). If you rely on page refreshes every time user swipes, this might be an issue.
Another option is creating a custom view for your RecyclerView page and adding a scroll value to be set from outside, e.g.
// in your custom page view
private RecyclerView.Adapter adapter;
private boolean needToScroll;
public void setNeedToScroll(boolean needToScroll) {
this.needToScroll = needToScroll;
// if adapter is not null (i.e. already set), scroll as is
// and set the value to false
if (adapter != null) {
this.needToScroll = false;
scrollBy(0, 200);
}
}
// and then in the place where you define your adapter, but after setting it
if (needToScroll) {
needToScroll = false;
scrollBy(0, 200);
}
Finally your view pager scroll listener
#Override
public void onPageScrolled(int position, float offset, int offsetPx) {
if(scrollNeeded()) {
Page view1 = getPage(position - 1);
Page view2 = getPage(position + 1);
view1.needToScroll(true);
view2.needToScroll(true);
}
}
I have been working on a ListViewidea where it keeps scrolling automatically with no user interaction and that is absolutely doable using the android APIs for instance smoothScrollToPositionFromTop.
I have implemented ListView BaseAdapter where it load items forever (almost) to get a non stopping self repeated ListView.
What I want to achieve here is to keep myListViewscrolling forever with certain speed (slow) to make items clear and readable while scrolling down, I not sure yet if ListView is my best choice here.
below is a snippet of what I am trying to do. the result is good somehow but it's not smooth enough, I can feel the ListView flickers.
I need to improve smoothness, efficiency and control the speed
new Thread(new Runnable() {
#Override
public void run() {
int listViewSize = mListView.getAdapter().getCount();
for (int index = 0; index < listViewSize ; index++) {
mListView.smoothScrollToPositionFromTop(mListViewA.getLastVisiblePosition() + 100, 0, 6000);
try {
// it helps scrolling to stay smooth as possible (by experiment)
Thread.sleep(60);
} catch (InterruptedException e) {
}
}
}
}).start();
I suggest, thath your adapter implemented in effective way.
so this code is just scrolls listview
you need to try another values of variables
final long totalScrollTime = Long.MAX_VALUE; //total scroll time. I think that 300 000 000 years is close enouth to infinity. if not enought you can restart timer in onFinish()
final int scrollPeriod = 20; // every 20 ms scoll will happened. smaller values for smoother
final int heightToScroll = 20; // will be scrolled to 20 px every time. smaller values for smoother scrolling
listView.post(new Runnable() {
#Override
public void run() {
new CountDownTimer(totalScrollTime, scrollPeriod ) {
public void onTick(long millisUntilFinished) {
listView.scrollBy(0, heightToScroll);
}
public void onFinish() {
//you can add code for restarting timer here
}
}.start();
}
});
Here a few pointers : Simulate onFling() programmatically instead of detecting it (Android)
and Programmatically Fling ListView Android
It's hard to figure out what you call smooth enough in your case. Usually smoothness problems are related to a non optimal usage of listviews and troubles in either cell layouts or view creation / recycling inside the getView method of adapters.
Do you use a placeholder ?
An important thing to consider is also Drawables usage.
I never achieved what you are looking for, but a simple idea that comes to mind is :
find a way to scroll the view of 1 position or 2.
use a ring buffer inside your adapter. For instance let's say you got 100 items in your list of items. Then at the beginning, item 0 of the listview is item 0 of your list. When listview is scrolled up of 1 item, then item 0 of listview should become item 1 in your list. Thus the problem would not be scrolling but more syncing with scrolling and displaying an endless list of items.