I have a method that will check if the last element in a RecyclerView is completely visible by the user, so far I have this code
The problem is how to check if the RecyclerView has reached it's bottom ?
PS I have items dividers
public void scroll_btn_visibility_controller(){
if(/**last item is visible to user*/){
//This is the Bottom of the RecyclerView
Scroll_Top_Btn.setVisibility(View.VISIBLE);
}
else(/**last item is not visible to user*/){
Scroll_Top_Btn.setVisibility(View.INVISIBLE);
}
}
UPDATE : This is one of the attempts I tried
boolean isLastVisible() {
LinearLayoutManager layoutManager = ((LinearLayoutManager)rv.getLayoutManager());
int pos = layoutManager.findLastCompletelyVisibleItemPosition();
int numItems = disp_adapter.getItemCount();
return (pos >= numItems);
}
public void scroll_btn_visibility_controller(){
if(isLastVisible()){
Scroll_Top.setVisibility(View.VISIBLE);
}
else{
Scroll_Top.setVisibility(View.INVISIBLE);
}
}
so far no success I think there is something wrong within these lines :
int pos = layoutManager.findLastCompletelyVisibleItemPosition();
int numItems = disp_adapter.getItemCount();
You can create a callback in your adapter which will send a message to your activity/fragment every time when the last item is visible.
For example, you can implement this idea in onBindViewHolder method
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if(position==(getItemCount()-1)){
// here goes some code
// callback.sendMessage(Message);
}
//do the rest of your stuff
}
UPDATE
Well, I know it's been a while but today I ran into the same problem, and I came up with a solution that works perfectly. So, I'll just leave it here if anybody ever needs it:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager layoutManager=LinearLayoutManager.class.cast(recyclerView.getLayoutManager());
int totalItemCount = layoutManager.getItemCount();
int lastVisible = layoutManager.findLastVisibleItemPosition();
boolean endHasBeenReached = lastVisible + 5 >= totalItemCount;
if (totalItemCount > 0 && endHasBeenReached) {
//you have reached to the bottom of your recycler view
}
}
});
You should use your code with following change:
boolean isLastVisible() {
LinearLayoutManager layoutManager =((LinearLayoutManager) rv.getLayoutManager());
int pos = layoutManager.findLastCompletelyVisibleItemPosition();
int numItems = rv.getAdapter().getItemCount();
return (pos >= numItems - 1);
}
Be careful, findLastCompletelyVisibleItemPosition() returns the position which start at 0. So, you should minus 1 after numItems.
Assuming you're using LinearLayoutManager, this method should do the trick:
boolean isLastVisible() {
LinearLayoutManager layoutManager = ((LinearLayoutManager)mRecyclerView.getLayoutManager());
int pos = layoutManager.findLastCompletelyVisibleItemPosition();
int numItems = mRecyclerView.getAdapter().getItemCount();
return (pos >= numItems);
}
try working with onScrollStateChanged it will solve your issue
Try this solution as it depends on how you want to implement the chat.
In your onCreate() method add the call to post to your recyclerview and implement the runable method, this to guarantee that the element has already been loaded and then execute the scrollToPosition method adding the last element of your list as a parameter.
recyclerView.post(new Runnable() {
#Override
public void run() {
recyclerView.scrollToPosition(yourList().size()-1);
}
});
Related
I have a scenario in which I need to know if a child view of a RecyclerView item is visible on screen.
In this case, each RecyclerView item has a TextView and I need to know if that TextView is fully visible on screen. I've already figured this part out, but now my question is this:
How can I make a call to the adapter from my fragment to let it know that the view is visible on screen? What best practice should I follow for this?
Here is my fragment class method where I get the visible child view:
private void getFirstVisibleChildView() {
int findFirstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
int findLastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
int [] positions = { findFirstVisibleItemPosition, findLastVisibleItemPosition };
PostAdapter.PostViewHolder viewHolder;
Rect scrollBounds = new Rect();
recyclerView.getDrawingRect(scrollBounds);
int[] location = new int[2];
for (int position : positions) {
RecyclerView.ViewHolder item = recyclerView.findViewHolderForAdapterPosition(position);
if (item instanceof PostAdapter.PostViewHolder) {
viewHolder = (PostAdapter.PostViewHolder) item;
viewHolder.getChildView().getLocationInWindow(location);
if (location[1] < 0 || location[1] > scrollBounds.bottom) {
// Not visible
} else {
// Visible
// How to call the RecyclerView adapter here and be able to manipulate it?
// Custom listener, direct call to ViewHolder, or some other method?
}
}
}
}
Thanks!
You can set addOnScrollListener on the RecyclerView, so it checkes if your specific view is fully shown whenever it's scrolled
And normally create a custom method in your adapter that you want to call within this listener when the above condition is met.
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (mRecyclerViewTafsir.getLayoutManager() == null) return;
// Here you can check if the particular textView is fully
// appeared on the screen >> You already did this part
mAdapter.callBack(); // call a method in the adapter when the condition is met
}
});
I am trying to make my RecyclerView loop back to the start of my list.
I have searched all over the internet and have managed to detect when I have reached the end of my list, however I am unsure where to proceed from here.
This is what I am currently using to detect the end of the list (found here):
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if ( (visibleItemCount+pastVisiblesItems) >= totalItemCount) {
loading = false;
Log.v("...", ""+visibleItemCount);
}
}
}
When scrolled to the end, I would like to views to be visible while the displaying data from the top of the list or when scrolled to the top of the list I would display data from the bottom of the list.
For example:
View1 View2 View3 View4 View5
View5 View1 View2 View3 View4
There is no way of making it infinite, but there is a way to make it look like infinite.
in your adapter override getCount() to return something big like Integer.MAX_VALUE:
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
in getItem() and getView() modulo divide (%) position by real item number:
#Override
public Fragment getItem(int position) {
int positionInList = position % fragmentList.size();
return fragmentList.get(positionInList);
}
at the end, set current item to something in the middle (or else, it would be endless only in downward direction).
// scroll to middle item
recyclerView.getLayoutManager().scrollToPosition(Integer.MAX_VALUE / 2);
The other solutions i found for this problem work well enough, but i think there might be some memory issues returning Integer.MAX_VALUE in getCount() method of recycler view.
To fix this, override getItemCount() method as below :
#Override
public int getItemCount() {
return itemList == null ? 0 : itemList.size() * 2;
}
Now wherever you are using the position to get the item from the list, use below
position % itemList.size()
Now add scrollListener to your recycler view
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition();
if (firstItemVisible != 0 && firstItemVisible % itemList.size() == 0) {
recyclerView.getLayoutManager().scrollToPosition(0);
}
}
});
Finally to start auto scrolling, call the method below
public void autoScroll() {
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
#Override
public void run() {
recyclerView.scrollBy(2, 0);
handler.postDelayed(this, 0);
}
};
handler.postDelayed(runnable, 0);
}
I have created a LoopingLayoutManager that fixes this issue.
It works without having to modify the adapter, which allows for greater flexibility and reusability.
It comes fully featured with support for:
Vertical and Horizontal Orientations
LTR and RTL
ReverseLayout for both orientations, as well as LTR, and RTL
Public functions for finding items and positions
Public functions for scrolling programmatically
Snap Helper support
Accessibility (TalkBack and Voice Access) support
And it is hosted on maven central, which means you just need to add it as a dependency in your build.gradle file:
dependencies {
implementation 'com.github.beksomega:loopinglayout:0.3.1'
}
and change your LinearLayoutManager to a LoopingLayoutManager.
It has a suite of 132 unit tests that make me confident it's stable, but if you find any bugs please put up an issue on the github!
I hope this helps!
In addition to solution above.
For endless recycler view in both sides you should add something like that:
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition()
if (firstItemVisible != 1 && firstItemVisible % songs.size == 1) {
linearLayoutManager.scrollToPosition(1)
}
val firstCompletelyItemVisible = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
if (firstCompletelyItemVisible == 0) {
linearLayoutManager.scrollToPositionWithOffset(songs.size, 0)
}
}
})
And upgrade your getItemCount() method:
#Override
public int getItemCount() {
return itemList == null ? 0 : itemList.size() * 2 + 1;
}
It is work like unlimited down-scrolling, but in both directions. Glad to help!
Amended #afanit's solution to prevent the infinite scroll from momentarily halting when scrolling in the reverse direction (due to waiting for the 0th item to become completely visible, which allows the scrollable content to run out before scrollToPosition() is called):
val firstItemPosition = layoutManager.findFirstVisibleItemPosition()
if (firstItemPosition != 1 && firstItemPosition % items.size == 1) {
layoutManager.scrollToPosition(1)
} else if (firstItemPosition == 0) {
layoutManager.scrollToPositionWithOffset(items.size, -recyclerView.computeHorizontalScrollOffset())
}
Note the use of computeHorizontalScrollOffset() because my layout manager is horizontal.
Also, I found that the minimum return value from getItemCount() for this solution to work is items.size + 3. Items with position larger than this are never reached.
I was running into OOM issues with Glide and other APIs and created this Implementation using the Duplicate End Caps inspired by this post for an iOS build.
Might look intimidating but its literally just copying the RecyclerView class and updating two methods in your RecyclerView Adapter. All it is doing is that once it hits the end caps, it does a quick no-animation transition to either ends of the adapter's ViewHolders to allow continuous cycling transitions.
http://iosdevelopertips.com/user-interface/creating-circular-and-infinite-uiscrollviews.html
class CyclingRecyclerView(
context: Context,
attrs: AttributeSet?
) : RecyclerView(context, attrs) {
// --------------------- Instance Variables ------------------------
private val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
// The total number of items in our RecyclerView
val itemCount = adapter?.itemCount ?: 0
// Only continue if there are more than 1 item, otherwise, instantly return
if (itemCount <= 1) return
// Once the scroll state is idle, check what position we are in and scroll instantly without animation
if (newState == SCROLL_STATE_IDLE) {
// Get the current position
val pos = (layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
// If our current position is 0,
if (pos == 0) {
Log.d("AutoScrollingRV", "Current position is 0, moving to ${itemCount - 1} when item count is $itemCount")
scrollToPosition(itemCount - 2)
} else if (pos == itemCount - 1) {
Log.d("AutoScrollingRV", "Current position is ${itemCount - 1}, moving to 1 when item count is $itemCount")
scrollToPosition(1)
} else {
Log.d("AutoScrollingRV", "Curren position is $pos")
}
}
}
}
init {
addOnScrollListener(onScrollListener)
}
}
For the Adapter, just make sure to update 2 methods, in my case, viewModels is just my data structure that contains the data that I send over to my ViewHolders
override fun getItemCount(): Int = if (viewModels.size > 1) viewModels.size + 2 else viewModels.size
and on ViewHolder, you just retrieve the adjusted index's data
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val adjustedPos: Int =
if (viewModels.size > 1) {
when (position) {
0 -> viewModels.lastIndex
viewModels.size + 1 -> 0
else -> position - 1
}
} else {
position
}
holder.bind(viewModels[adjustedPos])
}
The previous implementation's hurt me haha, seemed way to hacky to just add a crazy amount of items, big problem when you run into Multiple cards with an Integer.MAX_VALUE nested RecyclerView. This approach fixed all the problems of OOM since it only necessarily creates 2 and ViewHolders.
Endless recyclerView in both sides
Add onScrollListener at your recyclerview
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstItemVisible = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
if (firstItemVisible != 1 && firstItemVisible % itemList.size() == 1) {
((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPosition(1);
}
int firstCompletelyItemVisible = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
if (firstCompletelyItemVisible == 0)
{}
if (firstItemVisible != RecyclerView.NO_POSITION
&& firstItemVisible== recyclerView.getAdapter().getItemCount()%itemList.size() - 1)
{
((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(itemList.size() + 1, 0);
}
}
});
In your adapter override the getItemCount method
#Override
public int getItemCount()
{
return itemList == null ? 0 : itemList.size() * 2 + 1;
}
I have one RecyclerView and I added list of data into the RecyclerView. I wanted to add more data in list, when last RecyclerView item is visible on screen. After that I want to make a web service call and update the RecyclerView data. How can I achieve this?
Any suggestions?
One option would involve editing your LayoutManager. The idea here is to find the position of the last visible item. If that position is equal to the last item of your dataset, then you should trigger a reload.
#Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
final int result = super.scrollVerticallyBy(dy, recycler, state);
if (findLastVisibleItemPosition() == mData.length - 1) {
loadMoreData();
}
return result;
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
super.onLayoutChildren(recycler, state);
if (findLastVisibleItemPosition() == mData.length - 1) {
loadMoreData();
}
}
Alternatively, you could do this via your adapter's onBindViewHolder method, although this is admittedly a bit of a "hack":
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == mData.length - 1) {
// load more data here.
}
/// binding logic
}
3rd option would be to add an OnScrollListener to the RecyclerView. #velval's answer on this page explains this well.
Regardless which option you go for, you should also include code to prevent the data load logic from triggering too many times (e.g., before the previous request to fetch more data completes and returns new data).
If someone stumble across this post this is a quick and simple tutorial on how to do it:
All you need to do is:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = lm.getChildCount();
int totalItemCount = lm.getItemCount();
int firstVisibleItemPosition= lm.findFirstVisibleItemPosition();
// Load more if we have reach the end to the recyclerView
if ( (visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
});
Then your loadMoreItems() should look something like this:
private void loadMoreItems() {
// init offset=0 the frist time and increase the offset + the PAGE_SIZE when loading more items
queryOffset = queryOffset + PAGE_SIZE;
// HERE YOU LOAD the next batch of items
List<Items> newItems = loadItems(queryOffset, PAGE_SIZE);
if (newItems.size() > 0) {
items.addAll(newItems);
adapter.notifyDataSetChanged();
}
}
Seen many of the above answers but my answer is different one and it will work in your cases also. My approach is based on scroll state of recylerview. Maintain below variable "check" and this should update only once when api responds. Put below code in your api response. If you want to handle last item only on every call of api.
final boolean[] check = {true};
recyclerView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (!recyclerView.canScrollVertically(1)) {
// last item of recylerview reached.
if (check[0]) {
//your code for last reached item
scroll_handler.setVisibility(View.GONE);
}
} else {
scroll_handler.setVisibility(View.VISIBLE);
check[0] = false;
}
}
});
If you want to handle your last item every time then do it as below
recyclerView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (!recyclerView.canScrollVertically(1))
// Bottom of recyler view.
arrow_img.setRotation(180);
}
}
});
See also Android - Detect when the last item in a RecyclerView is visible.
private fun isLastItemVisible(): Boolean {
val layoutManager = recycler_view.layoutManager
val position = layoutManager.findLastCompletelyVisibleItemPosition()
return position >= adapter.itemCount - 1
}
I am trying to make my RecyclerView loop back to the start of my list.
I have searched all over the internet and have managed to detect when I have reached the end of my list, however I am unsure where to proceed from here.
This is what I am currently using to detect the end of the list (found here):
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if ( (visibleItemCount+pastVisiblesItems) >= totalItemCount) {
loading = false;
Log.v("...", ""+visibleItemCount);
}
}
}
When scrolled to the end, I would like to views to be visible while the displaying data from the top of the list or when scrolled to the top of the list I would display data from the bottom of the list.
For example:
View1 View2 View3 View4 View5
View5 View1 View2 View3 View4
There is no way of making it infinite, but there is a way to make it look like infinite.
in your adapter override getCount() to return something big like Integer.MAX_VALUE:
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
in getItem() and getView() modulo divide (%) position by real item number:
#Override
public Fragment getItem(int position) {
int positionInList = position % fragmentList.size();
return fragmentList.get(positionInList);
}
at the end, set current item to something in the middle (or else, it would be endless only in downward direction).
// scroll to middle item
recyclerView.getLayoutManager().scrollToPosition(Integer.MAX_VALUE / 2);
The other solutions i found for this problem work well enough, but i think there might be some memory issues returning Integer.MAX_VALUE in getCount() method of recycler view.
To fix this, override getItemCount() method as below :
#Override
public int getItemCount() {
return itemList == null ? 0 : itemList.size() * 2;
}
Now wherever you are using the position to get the item from the list, use below
position % itemList.size()
Now add scrollListener to your recycler view
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition();
if (firstItemVisible != 0 && firstItemVisible % itemList.size() == 0) {
recyclerView.getLayoutManager().scrollToPosition(0);
}
}
});
Finally to start auto scrolling, call the method below
public void autoScroll() {
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
#Override
public void run() {
recyclerView.scrollBy(2, 0);
handler.postDelayed(this, 0);
}
};
handler.postDelayed(runnable, 0);
}
I have created a LoopingLayoutManager that fixes this issue.
It works without having to modify the adapter, which allows for greater flexibility and reusability.
It comes fully featured with support for:
Vertical and Horizontal Orientations
LTR and RTL
ReverseLayout for both orientations, as well as LTR, and RTL
Public functions for finding items and positions
Public functions for scrolling programmatically
Snap Helper support
Accessibility (TalkBack and Voice Access) support
And it is hosted on maven central, which means you just need to add it as a dependency in your build.gradle file:
dependencies {
implementation 'com.github.beksomega:loopinglayout:0.3.1'
}
and change your LinearLayoutManager to a LoopingLayoutManager.
It has a suite of 132 unit tests that make me confident it's stable, but if you find any bugs please put up an issue on the github!
I hope this helps!
In addition to solution above.
For endless recycler view in both sides you should add something like that:
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition()
if (firstItemVisible != 1 && firstItemVisible % songs.size == 1) {
linearLayoutManager.scrollToPosition(1)
}
val firstCompletelyItemVisible = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
if (firstCompletelyItemVisible == 0) {
linearLayoutManager.scrollToPositionWithOffset(songs.size, 0)
}
}
})
And upgrade your getItemCount() method:
#Override
public int getItemCount() {
return itemList == null ? 0 : itemList.size() * 2 + 1;
}
It is work like unlimited down-scrolling, but in both directions. Glad to help!
Amended #afanit's solution to prevent the infinite scroll from momentarily halting when scrolling in the reverse direction (due to waiting for the 0th item to become completely visible, which allows the scrollable content to run out before scrollToPosition() is called):
val firstItemPosition = layoutManager.findFirstVisibleItemPosition()
if (firstItemPosition != 1 && firstItemPosition % items.size == 1) {
layoutManager.scrollToPosition(1)
} else if (firstItemPosition == 0) {
layoutManager.scrollToPositionWithOffset(items.size, -recyclerView.computeHorizontalScrollOffset())
}
Note the use of computeHorizontalScrollOffset() because my layout manager is horizontal.
Also, I found that the minimum return value from getItemCount() for this solution to work is items.size + 3. Items with position larger than this are never reached.
I was running into OOM issues with Glide and other APIs and created this Implementation using the Duplicate End Caps inspired by this post for an iOS build.
Might look intimidating but its literally just copying the RecyclerView class and updating two methods in your RecyclerView Adapter. All it is doing is that once it hits the end caps, it does a quick no-animation transition to either ends of the adapter's ViewHolders to allow continuous cycling transitions.
http://iosdevelopertips.com/user-interface/creating-circular-and-infinite-uiscrollviews.html
class CyclingRecyclerView(
context: Context,
attrs: AttributeSet?
) : RecyclerView(context, attrs) {
// --------------------- Instance Variables ------------------------
private val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
// The total number of items in our RecyclerView
val itemCount = adapter?.itemCount ?: 0
// Only continue if there are more than 1 item, otherwise, instantly return
if (itemCount <= 1) return
// Once the scroll state is idle, check what position we are in and scroll instantly without animation
if (newState == SCROLL_STATE_IDLE) {
// Get the current position
val pos = (layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
// If our current position is 0,
if (pos == 0) {
Log.d("AutoScrollingRV", "Current position is 0, moving to ${itemCount - 1} when item count is $itemCount")
scrollToPosition(itemCount - 2)
} else if (pos == itemCount - 1) {
Log.d("AutoScrollingRV", "Current position is ${itemCount - 1}, moving to 1 when item count is $itemCount")
scrollToPosition(1)
} else {
Log.d("AutoScrollingRV", "Curren position is $pos")
}
}
}
}
init {
addOnScrollListener(onScrollListener)
}
}
For the Adapter, just make sure to update 2 methods, in my case, viewModels is just my data structure that contains the data that I send over to my ViewHolders
override fun getItemCount(): Int = if (viewModels.size > 1) viewModels.size + 2 else viewModels.size
and on ViewHolder, you just retrieve the adjusted index's data
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val adjustedPos: Int =
if (viewModels.size > 1) {
when (position) {
0 -> viewModels.lastIndex
viewModels.size + 1 -> 0
else -> position - 1
}
} else {
position
}
holder.bind(viewModels[adjustedPos])
}
The previous implementation's hurt me haha, seemed way to hacky to just add a crazy amount of items, big problem when you run into Multiple cards with an Integer.MAX_VALUE nested RecyclerView. This approach fixed all the problems of OOM since it only necessarily creates 2 and ViewHolders.
Endless recyclerView in both sides
Add onScrollListener at your recyclerview
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstItemVisible = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
if (firstItemVisible != 1 && firstItemVisible % itemList.size() == 1) {
((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPosition(1);
}
int firstCompletelyItemVisible = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
if (firstCompletelyItemVisible == 0)
{}
if (firstItemVisible != RecyclerView.NO_POSITION
&& firstItemVisible== recyclerView.getAdapter().getItemCount()%itemList.size() - 1)
{
((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(itemList.size() + 1, 0);
}
}
});
In your adapter override the getItemCount method
#Override
public int getItemCount()
{
return itemList == null ? 0 : itemList.size() * 2 + 1;
}
I'm looking for a way to scroll a RecyclerView to show the selected item on top.
In a ListView I was able to do that by using scrollTo(x,y) and getting the top of the element that need to be centered.
Something like:
#Override
public void onItemClick(View v, int pos){
mylistView.scrollTo(0, v.getTop());
}
The problem is that the RecyclerView returns an error when using it's scrollTo method saying
RecyclerView does not support scrolling to an absolute position
How can I scroll a RecyclerView to put the selected item at the top of the view?
If you are using the LinearLayoutManager or Staggered GridLayoutManager, they each have a scrollToPositionWithOffset method that takes both the position and also the offset of the start of the item from the start of the RecyclerView, which seems like it would accomplish what you need (setting the offset to 0 should align with the top).
For instance:
//Scroll item 2 to 20 pixels from the top
linearLayoutManager.scrollToPositionWithOffset(2, 20);
If you looking for vertical LinearLayout Manager you can achieve smooth scrolling using a custom LinearSmoothScroller:
import android.content.Context;
import android.graphics.PointF;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
public class SnappingLinearLayoutManager extends LinearLayoutManager {
public SnappingLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
private class TopSnappedSmoothScroller extends LinearSmoothScroller {
public TopSnappedSmoothScroller(Context context) {
super(context);
}
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return SnappingLinearLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
#Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}
}
}
use an instance of the layoutmanager in recycle view and then calling recyclerView.smoothScrollToPosition(pos); will smooth scroll to selected position to top of the recycler view
//Scroll item pos
linearLayoutManager.scrollToPositionWithOffset(pos, 0);
You just need to call recyclerview.scrollToPosition(position). That's fine!
If you want to call it in adapter, just let your adapter has the instance of recyclerview or the activity or fragment which contains recyclerview,than implements the method getRecyclerview() in them.
I hope it can help you.
If you want to scroll automatic without show scroll motion then you need to write following code:
mRecyclerView.getLayoutManager().scrollToPosition(position);
If you want to display scroll motion then you need to add following code.
=>Step 1: You need to declare SmoothScroller.
RecyclerView.SmoothScroller smoothScroller = new
LinearSmoothScroller(this.getApplicationContext()) {
#Override
protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
};
=>step 2: You need to add this code any event you want to perform scroll to specific position.
=>First you need to set target position to SmoothScroller.
smoothScroller.setTargetPosition(position);
=>Then you need to set SmoothScroller to LayoutManager.
mRecyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
just call this method simply:
((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(yourItemPosition,0);
instead of:
recyclerView.scrollToPosition(yourItemPosition);
same with speed regulator
public class SmoothScrollLinearLayoutManager extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 110f;
private Context mContext;
public SmoothScrollLinearLayoutManager(Context context,int orientation, boolean reverseLayout) {
super(context,orientation,reverseLayout);
mContext = context;
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext()){
//This controls the direction in which smoothScroll looks for your view
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return new PointF(0, 1);
}
//This returns the milliseconds it takes to scroll one pixel.
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
private class TopSnappedSmoothScroller extends LinearSmoothScroller {
public TopSnappedSmoothScroller(Context context) {
super(context);
}
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return SmoothScrollLinearLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
#Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}
}
}
Try what worked for me cool!
Create a variable private static int displayedposition = 0;
Now for the position of your RecyclerView in your Activity.
myRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager llm = (LinearLayoutManager) myRecyclerView.getLayoutManager();
displayedposition = llm.findFirstVisibleItemPosition();
}
});
Place this statement where you want it to place the former site displayed in your view .
LinearLayoutManager llm = (LinearLayoutManager) mRecyclerView.getLayoutManager();
llm.scrollToPositionWithOffset(displayedposition , youList.size());
Well that's it , it worked fine for me \o/
what i did to restore the scroll position after refreshing the RecyclerView on button clicked:
if (linearLayoutManager != null) {
index = linearLayoutManager.findFirstVisibleItemPosition();
View v = linearLayoutManager.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop());
Log.d("TAG", "visible position " + " " + index);
}
else{
index = 0;
}
linearLayoutManager = new LinearLayoutManager(getApplicationContext());
linearLayoutManager.scrollToPositionWithOffset(index, top);
getting the offset of the first visible item from the top before creating the linearLayoutManager object and after instantiating it the scrollToPositionWithOffset of the LinearLayoutManager object was called.
I don't know why I didn't find the best answer but its really simple.
recyclerView.smoothScrollToPosition(position);
No errors
Creates Animations
What i may add here is how to make it work together with DiffUtil and ListAdapter
You may note that calling recyclerView.scrollToPosition(pos) or (recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(pos, offset) wouldn't work if called straight after adapter.submitList. It is because the differ looks for changes in a background thread and then asynchronously notifies adapter about changes. On a SO i have seen several wrong answers with unnecessary delays & etc to solve this.
To handle the situation properly the submitList has a callback which is invoked when changes have been applied.
So the proper kotlin implementations in this case are:
//memorise target item here and a scroll offset if needed
adapter.submitList(items) {
val pos = /* here you may find a new position of the item or just use just a static position. It depends on your case */
recyclerView.scrollToPosition(pos)
}
//or
adapter.submitList(items) { recyclerView.smoothScrollToPosition(pos) }
//or etc
adapter.submitList(items) { (recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(pos, offset) }
Introduction
None of the answers explain how to show last item(s) at the top. So, the answers work only for items that still have enough items above or below them to fill the remaining RecyclerView. For instance, if there are 59 elements and a 56-th element is selected it should be at the top as in the picture below:
So, let's see how to implement this in the next paragraph.
Solution
We could handle those cases by using linearLayoutManager.scrollToPositionWithOffset(pos, 0) and additional logic in the Adapter of RecyclerView - by adding a custom margin below the last item (if the last item is not visible then it means there's enough space fill the RecyclerView). The custom margin could be a difference between the root view height and the item height. So, your Adapter for RecyclerView would look as follows:
...
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
...
int bottomHeight = 0;
int itemHeight = holder.itemView.getMeasuredHeight();
// if it's the last item then add a bottom margin that is enough to bring it to the top
if (position == mDataSet.length - 1) {
bottomHeight = Math.max(0, mRootView.getMeasuredHeight() - itemHeight);
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)holder.itemView.getLayoutParams();
params.setMargins(0, 0, params.rightMargin, bottomHeight);
holder.itemView.setLayoutParams(params);
...
}
...
If your LayoutManager is LinearLayoutManager you can use scrollToPositionWithOffset(position,0); on it and it will make your item the first visible item in the list. Otherwise, you can use smoothScrollToPosition on the RecyclerView directly.
I ended up using the below code.
RecyclerView.LayoutManager layoutManager = mainList.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
// Scroll to item and make it the first visible item of the list.
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, 0);
} else {
mainList.smoothScrollToPosition(position);
}
scroll at particular position
and this helped me alot.
by click listener you can get the position in your adapter
layoutmanager.scrollToPosition(int position);
In my case my RecyclerView have a padding top like this
<android.support.v7.widget.RecyclerView
...
android:paddingTop="100dp"
android:clipToPadding="false"
/>
Then for scroll a item to top, I need to
recyclerViewLinearLayoutManager.scrollToPositionWithOffset(position, -yourRecyclerView.getPaddingTop());
please note that if scrollToPosition not work notice that your RecyclerView was inside a NestedScrollView; refer to this post
This is pretty simple
recyclerView.scrollToPosition(position)
If you've Recycler view inside nestedscrollview :
val y = recyclerview.getChildAt(0).y
recyclerview.smoothScrollTo(0, y.toInt())
If your Recycler view is not inside nestedscrollview :
recyclerview.smoothScrollToPosition(index)
or
recyclerview.layoutManager?.smoothScrollToPosition(recyclerview, null ,index)
I use the code below to smooth-scroll an item (thisView) to the top.
It works also for GridLayoutManager with views of different heights:
View firstView = mRecyclerView.getChildAt(0);
int toY = firstView.getTop();
int firstPosition = mRecyclerView.getChildAdapterPosition(firstView);
View thisView = mRecyclerView.getChildAt(thisPosition - firstPosition);
int fromY = thisView.getTop();
mRecyclerView.smoothScrollBy(0, fromY - toY);
Seems to work good enough for a quick solution.