RecyclerView infinite scroll in both directions - android

I want to implement a RecyclerView that never finishes. Before the first item there is the last item and after the last item is scrolled, the first item is shown.
How can I do that?
What Im trying to achieve is a music player that shows the current song, the song before and after.
Like this:
(ps. is there a better way than using a recyclerview?)

The way I have seen this done is to define the number of items that you have to display as Integer.MAX_VALUE. In onBindViewHolder() of the adapter you will have something like:
#Override
public int getItemCount() {
return (mItems == null) ? 0 : Integer.MAX_VALUE;
}
Now you can't really have Integer.MAX_VALUE items to display, so this is a small lie to the RecyclerView. In your code, map the index value from 0...Integer.MAX_VALUE-1 to 0...your_item_count-1 like this in onBindViewHolder():
int realPos = position % mItems.size();
Use realPos instead of the position that the RecyclerView passes in.
All that is left is to scroll the RecyclerView to the middle of the range:
mRecycler.scrollToPosition(Integer.MAX_VALUE / 2);
This works OK, but is it perfect? No, but close enough; I don't think anyone will scroll enough to see the flaw. (It will stop wrapping at the extremes.)

Related

Why does LinearLayoutManager.childAt() returns null?

I have a recylcerview where the method shown below gets called on each selection of an item. The method only runs sucessfully when I do not scroll the recyclerview, so the firstvisible is 0. The moment I scroll down a bit and select something the first and last visible are set correctly in the method, but as can be seen in the screenshot, for reasons I do not understand the childAt returns null instead of the view I can see on my app screen. In the screenshot for position 7, the first 4-6 returned a child.
Can someone explain to me how this can happen? From my pov, getChildAt() should always return a view in this scenario.
first and last are going to be the adapter positions of the data and not the position as laid out in the layout manager. See LinearLayoutManager#findFirstVisibleItemPosition. The children will always start with zero and increase from there.
That is why it works before your scroll since the child at the zeroth index in the layout manager is also the zeroth item in the adapter.
Here is a discussion about the various positions in RecyclerView.
It looks like you want to make changes to all visible items. Your first and last variables will have the correct start/end adapter positions that correspond to what is visible on the screen. You need the adapter positions to call the various "notify" methods.
So, given the adapter positions, we need a map to the views that are represented on the screen. As an example, the following code loops through every visible view and changes the background color of each view.
LinearLayoutManager lm = (LinearLayoutManager) Recycler.getLayoutManager();
// Get adapter positions for first and last visible items on screen.
int firstVisible = lm.findFirstVisibleItemPosition();
int lastVisible = lm.findLastVisibleItemPosition();
for (int i = firstVisible; i <= lastVisible; i++) {
// Find the view that corresponds to this position in the adapter.
View visibleView = lm.findViewByPosition(i);
visibleView.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));
}
If you use the child methods of the layout manager, you will need to loop from zero to LayoutManager.getChildCount() - 1 to make the changes. You will see each attached view which, I believe, can exceed the number of visible views.

Infinite List in Android

I want to create an infinite list , when i scroll to the end of list , my list will show first item, second item ... at the end() . Such as :
item[0]
........
item[last]
item[0]
item[1]
The first thing comes to my mind is that I will double the list so it can show like I wished.
Unfortunately , it's a bad idea because size of my list will grow more and more , and loading a big list is not good for performance(obviously).
May you guys show me other solutions to handle this case ? Thank you very much .
Using RecyclerView, you can achieve what you want in few steps. The idea is to set adapter's item count to infinity, and place your first item to the middle of infinity.
In your Adapter override getItemCount() method like this.
#Override
public int getItemCount() {
return Integer.MAX_VALUE;// Not the infinity, but I'm not sure someone will scroll about 2 million items and find out that's a cheat
}
Then in the class where you use the RecyclerView, get LayoutManager and set starting position to the middle of adapter itemCount.
LinearLayoutManager layoutManager = getLayoutManager();
int pos = (Integer.MAX_VALUE / 2); //Voila, you are in the middle of infinity
layoutManager.scrollToPosition(pos);
yourAdapter.setLayoutManager(layoutManager);
By doing this you are able to scroll your list in any direction up to about 2 000 000 scrolls, without container size increasing and any performance issues.
You need to update your list when user reach bottom of the page.
You can keep track end of the page in on-scroll Listener.
Once user reach bottom of the or about to reach bottom of the page just double your list or extend it as per your requirement.
Extend or double your list is required because we need to store or display the data of each item in each row.
And Same thing happen in pagination concept.
Clearly understood we are not extending list unnecessary, we are extending when we really need.
You can visit this link for Endless Scrolling with AdapterViews and RecyclerView.
use onScrollListener() and when got to the end , use LIST.size()+VisibleItems (items from end of list that are visible) and put them first on array and renew your adapter data
sorry for very briefing Explanation !!!

How to programmatically snap to position on Recycler view with LinearSnapHelper

I have implemented a horizontal recyclerView with LinearSnapHelper, to implement a UI input that selects a particular configuration. Kinda like the old school number picker/selector or spinner. The item in the center is the selected position.
it works fine and all, but here's the problem. On initial start up, I need to programmatically set the position of the recycler view such that the selected item (the index of which was loaded from disk) is position in the center.
.scrollToPosition() wont work becuase it places the selected item in the begining.
now I know I can do all the math and calculate the x coordinate and manually set it, but thats a lot of redundant work because LinearSnapHelper is already doing this, and I feel like there should be a way to just reuse that logic, but with actually initiating a fling.
I need something like LinearSnapHelper.snapToPosition()
More general solution:
First scroll RecyclerView to make target item visible.
Than, take the object of target View and use SnapHelper to determine
distance for the final snap.
Finally scroll to target position.
NOTE: This works only because programmatically you are scrolling at the exact position & covering the missing distance by exact value using scrollBy instead of doing smooth scrolling
Code snippet:
mRecyclerView.scrollToPosition(selectedPosition);
mRecyclerView.post(() -> {
View view = mLayoutManager.findViewByPosition(selectedPosition);
if (view == null) {
Log.e(WingPickerView.class.getSimpleName(), "Cant find target View for initial Snap");
return;
}
int[] snapDistance = mSnapHelper.calculateDistanceToFinalSnap(mLayoutManager, view);
if (snapDistance[0] != 0 || snapDistance[1] != 0) {
mRecyclerView.scrollBy(snapDistance[0], snapDistance[1]);
}
}
});
Try calling smoothScrollToPosition on the RecyclerView object, and passing the position index (int)
mRecyclerView.smoothScrollToPosition(position);
Worked for me with a LinearLayoutManager and LinearSnapHelper. It animates the initial scroll, but at least snaps the item in position.
This is my first post on the stack, hope it helps :)
I have a recyclerView which I have added padding at the left and right with dummy views in the adapter. So that the first "actual" item can be snapped to.
I couldn't get smoothScrollToPosition(0) to work though for the initial snap. I used the following
recycler.scrollBy(snapHelper.calculateDistanceToFinalSnap(binding.recycler.getLayoutManager(), recycler.getChildAt(1))[0], 0);
Isn't the nicest looking way, but seems to work!

RecycleView: how to go to a row without scrolling

If the recyclerview has a list of items and I want to go to a specific row immediately when user enters the UI, so I don't want the user to see it scroll to reach that row. Is it achievable?
Thanks.
You can use method scrollToPositionWithOffset (int position, int offset)
Scroll to the specified adapter position with the given offset from
resolved layout start.
See documentation
Example:
//Scroll to item position 2 with offset 0
RECYCLERVIEW_LAYOUT_MANAGER.scrollToPositionWithOffset(2, 0);
Hope this will help~
I would say it is impossible.
RecyclerView#scrollToPosition causes sort of animation because it does not know the height of all rows initially. Not all items are loaded immediatly but one by one as you scroll down the list.
I use ListView and I solved the issue by calling setSelection(position)

Android ListView y position

It seems like ListView doesn't expose its y-position in the same way a ScrollView does. In other words: I need to remember the exact position the ListView was scrolled to and set it when I return to the activity.
Just to clarify: I don't need the selected item... that's pretty straight forward. I need to restore the exact pixel-wise y position on the ListView.
Thanks!
I've used this successfully when saving (in onSaveInstanceState) and restoring (onCreate), when switching orientations:
Saving:
int savedPosition = list.getFirstVisiblePosition();
View firstVisibleView = list.getChildAt(0);
int savedListTop = (firstVisibleView == null) ? 0 : firstVisibleView.getTop();
Restoring:
if (savedPosition >= 0) { //initialized to -1
list.setSelectionFromTop(savedPosition, savedListTop);
}
This will precisely save the Y position. Well, it misses by a few pixels every once in a while.
When you are returning from another Activity, the ListView will remain scrolled to its original position that it was at when you left that ListView Activity. If you are updating the contents of the list make sure you just use notifyDataSetChanged() on the adapter, do not re-assign the adapter - this will reset the list to the top.
Check your onResume method in your ListView Activity, it might be re-assigning a list adapter.
If you need to remember an arbitrary scroll position that doesn't rely on the Activity stack, I am willing to bet that isn't possible besides just saving the current selected or first visible item. A ListView does not have a defined height. It is relative to both the number of items and content of those items.
Parcelable state = list.onSaveInstanceState();
// do stuff
list.onRestoreInstanceState(state);
Is the only correct way I know of to maintain exact position of a list. The above solution that's marked as correct bumps up/down a few pixels so not really the most professional solution.

Categories

Resources