Detect when RecyclerView reaches the bottom most position while scrolling - android

I have this code for a RecyclerView.
recyclerView = (RecyclerView)rootview.findViewById(R.id.fabric_recyclerView);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new RV_Item_Spacing(5));
FabricAdapter fabricAdapter=new FabricAdapter(ViewAdsCollection.getFabricAdsDetailsAsArray());
recyclerView.setAdapter(fabricAdapter);
I need to know when the RecyclerView reaches bottom most position while scrolling. Is it possible ? If yes, how ?

there is also a simple way to do it
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!recyclerView.canScrollVertically(1)) {
Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();
}
}
});
direction integers: -1 for up, 1 for down, 0 will always return false.

Use this code for avoiding repeated calls
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!recyclerView.canScrollVertically(1) && newState==RecyclerView.SCROLL_STATE_IDLE) {
Log.d("-----","end");
}
}
});

Just implement a addOnScrollListener() on your recyclerview. Then inside the scroll listener implement the code below.
RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (mIsLoading)
return;
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int pastVisibleItems = mLayoutManager.findFirstVisibleItemPosition();
if (pastVisibleItems + visibleItemCount >= totalItemCount) {
//End of list
}
}
};

After not being satisfied with most the other answers in this thread, I found something I think is better and is not anywhere on here.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (!recyclerView.canScrollVertically(1) && dy > 0)
{
//scrolled to BOTTOM
}else if (!recyclerView.canScrollVertically(-1) && dy < 0)
{
//scrolled to TOP
}
}
});
This is simple and will hit exactly one time under all conditions when you have scrolled to the top or bottom.

Answer is in Kotlin, it will work in Java. IntelliJ should convert it for you if you copy and paste.
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener(){
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
// 3 lines below are not needed.
Log.d("TAG","Last visible item is: ${gridLayoutManager.findLastVisibleItemPosition()}")
Log.d("TAG","Item count is: ${gridLayoutManager.itemCount}")
Log.d("TAG","end? : ${gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1}")
if(gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1){
// We have reached the end of the recycler view.
}
super.onScrolled(recyclerView, dx, dy)
}
})
This will also work for LinearLayoutManager because it has the same methods used above. Namely findLastVisibleItemPosition() and getItemCount() (itemCount in Kotlin).

I was not getting a perfect solution by the above answers because it was triggering twice even on onScrolled
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if( !recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN))
context?.toast("Scroll end reached")
}
Alternative solution which I had found some days ago,
rv_repatriations.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN) && recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE
&& !isLoaded
) {
isLoaded = true
//do what you want here and after calling the function change the value of boolean
Log.e("RepatriationFragment", "Scroll end reached")
}
}
})
Using a boolean to ensure that it's not called multiple times when we hit the bottom.

Try This
I have used above answers it runs always when you will go at the end of recycler view,
If you want to check only one time whether it is on a bottom or not?
Example:- If I have the list of 10 items whenever I go on the bottom it will display me and again if I scroll top to bottom it will not print again, and if you add more lists and you go there it will again display.
Note:- Use this method when you deal with offset in hitting API
Create a class named as EndlessRecyclerViewScrollListener
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
public abstract class EndlessRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 5;
// The current offset index of data you have loaded
private int currentPage = 0;
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// Sets the starting page index
private int startingPageIndex = 0;
RecyclerView.LayoutManager mLayoutManager;
public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
}
// public EndlessRecyclerViewScrollListener() {
// this.mLayoutManager = layoutManager;
// visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
// }
public EndlessRecyclerViewScrollListener(StaggeredGridLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
}
public int getLastVisibleItem(int[] lastVisibleItemPositions) {
int maxSize = 0;
for (int i = 0; i < lastVisibleItemPositions.length; i++) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i];
}
else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i];
}
}
return maxSize;
}
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
#Override
public void onScrolled(RecyclerView view, int dx, int dy) {
int lastVisibleItemPosition = 0;
int totalItemCount = mLayoutManager.getItemCount();
if (mLayoutManager instanceof StaggeredGridLayoutManager) {
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
// get maximum element within the list
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
} else if (mLayoutManager instanceof GridLayoutManager) {
lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
} else if (mLayoutManager instanceof LinearLayoutManager) {
lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
}
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0) {
this.loading = true;
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && (totalItemCount > previousTotalItemCount)) {
loading = false;
previousTotalItemCount = totalItemCount;
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
currentPage++;
onLoadMore(currentPage, totalItemCount, view);
loading = true;
}
}
// Call this method whenever performing new searches
public void resetState() {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = 0;
this.loading = true;
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view);
}
use this class like this
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener( linearLayoutManager) {
#Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
Toast.makeText(getActivity(),"LAst",Toast.LENGTH_LONG).show();
}
});
Its running perfect at my end, commnent me if you are getting any issue

Kotlin Answer
You can use this Kotlin function for best practice of bottom scroll following to create infinite or endless scrolling.
// Scroll listener.
private fun setupListenerPostListScroll() {
val scrollDirectionDown = 1 // Scroll down is +1, up is -1.
var currentListSize = 0
mRecyclerView.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!recyclerView.canScrollVertically(scrollDirectionDown)
&& newState == RecyclerView.SCROLL_STATE_IDLE
) {
val listSizeAfterLoading = recyclerView.layoutManager!!.itemCount
// List has more item.
if (currentListSize != listSizeAfterLoading) {
currentListSize = listSizeAfterLoading
// Get more posts.
postListScrollUpAction(listSizeAfterLoading)
}
else { // List comes limit.
showToastMessageShort("No more items.")
}
}
}
})
}

There is my implementation, it is very useful for StaggeredGridLayout.
Usage :
private EndlessScrollListener scrollListener =
new EndlessScrollListener(new EndlessScrollListener.RefreshList() {
#Override public void onRefresh(int pageNumber) {
//end of the list
}
});
rvMain.addOnScrollListener(scrollListener);
Listener implementation :
class EndlessScrollListener extends RecyclerView.OnScrollListener {
private boolean isLoading;
private boolean hasMorePages;
private int pageNumber = 0;
private RefreshList refreshList;
private boolean isRefreshing;
private int pastVisibleItems;
EndlessScrollListener(RefreshList refreshList) {
this.isLoading = false;
this.hasMorePages = true;
this.refreshList = refreshList;
}
#Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
StaggeredGridLayoutManager manager =
(StaggeredGridLayoutManager) recyclerView.getLayoutManager();
int visibleItemCount = manager.getChildCount();
int totalItemCount = manager.getItemCount();
int[] firstVisibleItems = manager.findFirstVisibleItemPositions(null);
if (firstVisibleItems != null && firstVisibleItems.length > 0) {
pastVisibleItems = firstVisibleItems[0];
}
if (visibleItemCount + pastVisibleItems >= totalItemCount && !isLoading) {
isLoading = true;
if (hasMorePages && !isRefreshing) {
isRefreshing = true;
new Handler().postDelayed(new Runnable() {
#Override public void run() {
refreshList.onRefresh(pageNumber);
}
}, 200);
}
} else {
isLoading = false;
}
}
public void noMorePages() {
this.hasMorePages = false;
}
void notifyMorePages() {
isRefreshing = false;
pageNumber = pageNumber + 1;
}
interface RefreshList {
void onRefresh(int pageNumber);
} }

I was also searching for this question but I didn't find the answer that satisfied me, so I create own realization of recyclerView.
other solutions is less precise then mine. for example: if the last item is pretty big (lot of text) then callback of other solutions will come much earlier then recyclerView realy reached bottom.
my sollution fix this issue.
class CustomRecyclerView: RecyclerView{
abstract class TopAndBottomListener{
open fun onBottomNow(onBottomNow:Boolean){}
open fun onTopNow(onTopNow:Boolean){}
}
constructor(c:Context):this(c, null)
constructor(c:Context, attr:AttributeSet?):super(c, attr, 0)
constructor(c:Context, attr:AttributeSet?, defStyle:Int):super(c, attr, defStyle)
private var linearLayoutManager:LinearLayoutManager? = null
private var topAndBottomListener:TopAndBottomListener? = null
private var onBottomNow = false
private var onTopNow = false
private var onBottomTopScrollListener:RecyclerView.OnScrollListener? = null
fun setTopAndBottomListener(l:TopAndBottomListener?){
if (l != null){
checkLayoutManager()
onBottomTopScrollListener = createBottomAndTopScrollListener()
addOnScrollListener(onBottomTopScrollListener)
topAndBottomListener = l
} else {
removeOnScrollListener(onBottomTopScrollListener)
topAndBottomListener = null
}
}
private fun createBottomAndTopScrollListener() = object :RecyclerView.OnScrollListener(){
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
checkOnTop()
checkOnBottom()
}
}
private fun checkOnTop(){
val firstVisible = linearLayoutManager!!.findFirstCompletelyVisibleItemPosition()
if(firstVisible == 0 || firstVisible == -1 && !canScrollToTop()){
if (!onTopNow) {
onTopNow = true
topAndBottomListener?.onTopNow(true)
}
} else if (onTopNow){
onTopNow = false
topAndBottomListener?.onTopNow(false)
}
}
private fun checkOnBottom(){
var lastVisible = linearLayoutManager!!.findLastCompletelyVisibleItemPosition()
val size = linearLayoutManager!!.itemCount - 1
if(lastVisible == size || lastVisible == -1 && !canScrollToBottom()){
if (!onBottomNow){
onBottomNow = true
topAndBottomListener?.onBottomNow(true)
}
} else if(onBottomNow){
onBottomNow = false
topAndBottomListener?.onBottomNow(false)
}
}
private fun checkLayoutManager(){
if (layoutManager is LinearLayoutManager)
linearLayoutManager = layoutManager as LinearLayoutManager
else
throw Exception("for using this listener, please set LinearLayoutManager")
}
private fun canScrollToTop():Boolean = canScrollVertically(-1)
private fun canScrollToBottom():Boolean = canScrollVertically(1)
}
then in your activity/fragment:
override fun onCreate() {
customRecyclerView.layoutManager = LinearLayoutManager(context)
}
override fun onResume() {
super.onResume()
customRecyclerView.setTopAndBottomListener(this)
}
override fun onStop() {
super.onStop()
customRecyclerView.setTopAndBottomListener(null)
}
hope it will hepl someone ;-)

Using Kotlin
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (!recyclerView.canScrollVertically(1)) {
Toast.makeText(context, "Last", Toast.LENGTH_LONG).show();
}
}
})

I've seen to many responses for this question and I stand that all of them don't give accurate behavior as an outcome. However if you follow this approach I'm positive you'll get the best behavior.
rvCategories is your RecyclerView
categoriesList is the list passed to your adapter
binding.rvCategories.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val position = (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
if (position + 1 == categoriesList.size) {
// END OF RECYCLERVIEW IS REACHED
} else {
// END OF RECYCLERVIEW IS NOT REACHED
}
}
})

Most of the answers are poorly constructed and have some issues. One of the common issues is if the user scrolls fast, the end reached block executes multiple times I've found a solution, where the end block runs just 1 single time.
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (yourLayoutManager.findLastVisibleItemPosition() ==
yourLayoutManager.itemCount - 1 && !recyclerView.canScrollVertically(1)) {
Logger.log("End reached")
// Do your operations
}
super.onScrolled(recyclerView, dx, dy)
}
P.S. Sometimes if RecyclerView gets empty, the end listener might get called. As a solution, you can also add this check in the above code.
if (recyclerView.adapter?.itemCount!! > 0)

You can use this, if you put 1 thats will be indicated when you stay in end of list, if you want now when you stay in the start of the list you change 1 for -1
recyclerChat.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!recyclerView.canScrollVertically(1)) {
}
}
})

This is my solution after reading all answers in this post.
I only want to show loading when the last item is shown at the end of the list and listview length is larger than screen height, meaning if there's only one or two items in the list, won't show the loading.
private var isLoadMoreLoading: Boolean = false
mRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!isLoadMoreLoading) {
if (linearLayoutManager.findLastCompletelyVisibleItemPosition() == (list.size-1)) {
if (recyclerView.canScrollVertically(-1)) {
adapter?.addLoadMoreView()
loadMore()
}
}
}
}
})
private fun loadMore() {
isLoadMoreLoading = true
//call loadMore api
}
Because of this linearLayoutManager.findLastCompletelyVisibleItemPosition() == (list.size-1), we can know last item is shown, but we also need to know listview can scroll or not.
Therefore I added recyclerView.canScrollVertically(-1). Once you hit the bottom of the list, it cannot scroll down anymore. -1 means list can scroll up. That means listview length is larger than screen height.
This answer is in kotlin.

The most simple way to do it is in the adapter like this:
#Override
public void onBindViewHolder(HistoryMessageListAdapter.ItemViewHolder holder, int position) {
if (position == getItemCount()-1){
listener.onLastItemReached();
}
}
Because as soon as the last item is recycled the listener is triggered.

This is my solution:
val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
directionDown = dy > 0
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (recyclerView.canScrollVertically(1).not()
&& state != State.UPDATING
&& newState == RecyclerView.SCROLL_STATE_IDLE
&& directionDown) {
state = State.UPDATING
// TODO do what you want when you reach bottom, direction
// is down and flag for non-duplicate execution
}
}
}

We can use Interface for get the position
Interface :
Create an Interface for listener
public interface OnTopReachListener { void onTopReached(int position);}
Activity :
mediaRecycleAdapter = new MediaRecycleAdapter(Class.this, taskList); recycle.setAdapter(mediaRecycleAdapter); mediaRecycleAdapter.setOnSchrollPostionListener(new OnTopReachListener() {
#Override
public void onTopReached(int position) {
Log.i("Position","onTopReached "+position);
}
});
Adapter :
public void setOnSchrollPostionListener(OnTopReachListener topReachListener) {
this.topReachListener = topReachListener;}#Override public void onBindViewHolder(MyViewHolder holder, int position) {if(position == 0) {
topReachListener.onTopReached(position);}}

Use this method after declaring and initializing your recyclerView with adapter
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(dy > 0){ // Scrolling Down
//**Look at the condition inside if, this is how you can check, either you //have scrolled till last element of your recyclerView or not.**
//------------------------------------------------------------------------------
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (linearLayoutManager != null && linearLayoutManager.findLastCompletelyVisibleItemPosition() == recyclerViewList.size() - 1) {
//bottom of list!
}
//------------------------------------------------------------------------------
}else if(dy < 0){
// Scrolling Up
}
}
});

After a long search, I found the prefect solution, that only scrolls to bottom when you want to, and also maintains the smooth scroll behavior you get by using a ListAdapter:
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
scrollToBottomIfUserIsAtTheBottom(linearLayoutManager, positionStart)
}
})
private fun scrollToBottomIfUserIsAtTheBottom(linearLayout: LinearLayoutManager, positionStart: Int) {
val messageCount = adapter.itemCount
val lastVisiblePosition = linearLayout.findLastCompletelyVisibleItemPosition()
if (lastVisiblePosition == -1 ||
(positionStart >= (messageCount - 1) &&
lastVisiblePosition == (positionStart - 1)))
{
recyclerView.scrollToPosition(positionStart)
}
}
A few notes:
If you use a list adapter and you add a new item to the list each update, you must create a new list each time for the smooth scroll to work.
Don't use smoothScrollToPosition with the ListAdapter! It ruins the ListAdapter "smoothier" behavior that happens when the diff works good and it detects that the change between old and new list is in a newly added item.
linearLayoutManager is adapter.layoutManager as LinearLayoutManager

Try this,
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = recyclerView.getLayoutManager().getChildCount();
int totalItemCount = recyclerView.getLayoutManager().getItemCount();
int firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount) {
if(previousLast != lastItem) {
previousLast = lastItem;
load();
}
}
}
});

Kotlin version of ScrollListener with the ability to set the indent from the last element to load
class MyScrollListener(
private val indentForAction: Int = DEFAULT_INDENT_TO_INVOKE_ACTION,
private val onEndReached: () -> Unit,
) : RecyclerView.OnScrollListener() {
private var currentListSize = 0
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val linearLayoutManager = recyclerView.layoutManager as? LinearLayoutManager
?: error("ScrollListener works only with LinearLayoutManager")
val lastVisiblePosition = linearLayoutManager.findLastVisibleItemPosition()
val itemCount = linearLayoutManager.itemCount
if (lastVisiblePosition > itemCount - indentForAction && itemCount > 0) {
if (currentListSize != itemCount) {
currentListSize = itemCount
onEndReached.invoke()
}
}
}
private companion object {
const val DEFAULT_INDENT_TO_INVOKE_ACTION = 8
}
}

Related

Recycler View - Show only minimum elements and expand recycler view if user clicks expand [duplicate]

I would like to change ListView to RecyclerView. I want to use the onScroll of the OnScrollListener in RecyclerView to determine if a user scrolled to the end of the list.
How do I know if a user scrolls to the end of the list so that I can fetch new data from a REST service?
Thanks to #Kushal and this is how I implemented it
private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy > 0) { //check for scroll down
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
loading = false;
Log.v("...", "Last Item Wow !");
// Do pagination.. i.e. fetch new data
loading = true;
}
}
}
}
});
Don't forget to add
LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Make these variables.
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
Set on Scroll for recycler view.
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
// End has been reached
Log.i("Yaeye!", "end called");
// Do something
loading = true;
}
}
});
Note : Make sure you are using LinearLayoutManager as layout manager for RecyclerView.
LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
and for a grid
GridLayoutManager mLayoutManager;
mLayoutManager = new GridLayoutManager(getActivity(), spanCount);
mRecyclerView.setLayoutManager(mLayoutManager);
Have fun with your endless scrolls !! ^.^
Update : mRecyclerView.setOnScrollListener() is deprecated just replace with mRecyclerView.addOnScrollListener() and the warning will be gone! You can read more from this SO question.
Since Android now officially support Kotlin, here is an update for the same -
Make OnScrollListener
class OnScrollListener(val layoutManager: LinearLayoutManager, val adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>, val dataList: MutableList<Int>) : RecyclerView.OnScrollListener() {
var previousTotal = 0
var loading = true
val visibleThreshold = 10
var firstVisibleItem = 0
var visibleItemCount = 0
var totalItemCount = 0
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
visibleItemCount = recyclerView.childCount
totalItemCount = layoutManager.itemCount
firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
if (loading) {
if (totalItemCount > previousTotal) {
loading = false
previousTotal = totalItemCount
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
val initialSize = dataList.size
updateDataList(dataList)
val updatedSize = dataList.size
recyclerView.post { adapter.notifyItemRangeInserted(initialSize, updatedSize) }
loading = true
}
}
}
and add it to your RecyclerView like this
recyclerView.addOnScrollListener(OnScrollListener(layoutManager, adapter, dataList))
For a full code example, feel free to refer this Github repo.
For those who only want to get notified when the last item is totally shown, you can use View.canScrollVertically().
Here is my implementation:
public abstract class OnVerticalScrollListener
extends RecyclerView.OnScrollListener {
#Override
public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (!recyclerView.canScrollVertically(-1)) {
onScrolledToTop();
} else if (!recyclerView.canScrollVertically(1)) {
onScrolledToBottom();
} else if (dy < 0) {
onScrolledUp();
} else if (dy > 0) {
onScrolledDown();
}
}
public void onScrolledUp() {}
public void onScrolledDown() {}
public void onScrolledToTop() {}
public void onScrolledToBottom() {}
}
Note: You can use recyclerView.getLayoutManager().canScrollVertically() if you want to support API < 14.
Here is another approach. It will work with any layout manager.
Make Adapter class abstract
Then create an abstract method in adapter class (eg. load())
In onBindViewHolder check the position if last and call load()
Override the load() function while creating the adapter object in your activity or fragment.
In the overided load function implement your loadmore call
For a detail understanding I wrote a blog post and example project get it here
http://sab99r.com/blog/recyclerview-endless-load-more/
MyAdapter.java
public abstract class MyAdapter extends RecyclerView.Adapter<ViewHolder>{
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//check for last item
if ((position >= getItemCount() - 1))
load();
}
public abstract void load();
}
MyActivity.java
public class MainActivity extends AppCompatActivity {
List<Items> items;
MyAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
adapter=new MyAdapter(items){
#Override
public void load() {
//implement your load more here
Item lastItem=items.get(items.size()-1);
loadMore();
}
};
}
}
My answer is a modified version of Noor. I passed from a ListView where i had EndlessScrollListener (that you can find easily in many answers on SO) to a RecyclerView so i wanted a EndlessRecyclScrollListener to easily update my past listener.
So here is the code, hope it helps:
public abstract class EndlessScrollRecyclListener extends RecyclerView.OnScrollListener
{
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
private int startingPageIndex = 0;
private int currentPage = 0;
#Override
public void onScrolled(RecyclerView mRecyclerView, int dx, int dy)
{
super.onScrolled(mRecyclerView, dx, dy);
LinearLayoutManager mLayoutManager = (LinearLayoutManager) mRecyclerView
.getLayoutManager();
visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
onScroll(firstVisibleItem, visibleItemCount, totalItemCount);
}
public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount)
{
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0)
{
this.loading = true;
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && (totalItemCount > previousTotalItemCount))
{
loading = false;
previousTotalItemCount = totalItemCount;
currentPage++;
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem +
visibleThreshold))
{
onLoadMore(currentPage + 1, totalItemCount);
loading = true;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page, int totalItemsCount);
}
For me, it's very simple:
private boolean mLoading = false;
mList.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int totalItem = mLinearLayoutManager.getItemCount();
int lastVisibleItem = mLinearLayoutManager.findLastVisibleItemPosition();
if (!mLoading && lastVisibleItem == totalItem - 1) {
mLoading = true;
// Scrolled to bottom. Do something here.
mLoading = false;
}
}
});
Be careful with asynchronous jobs: mLoading must be changed at the end of the asynchronous jobs. Hope it will be helpful!
With the power of Kotlin's extension functions, the code can look a lot more elegant. Put this anywhere you want (I have it inside an ExtensionFunctions.kt file):
/**
* WARNING: This assumes the layout manager is a LinearLayoutManager
*/
fun RecyclerView.addOnScrolledToEnd(onScrolledToEnd: () -> Unit){
this.addOnScrollListener(object: RecyclerView.OnScrollListener(){
private val VISIBLE_THRESHOLD = 5
private var loading = true
private var previousTotal = 0
override fun onScrollStateChanged(recyclerView: RecyclerView,
newState: Int) {
with(layoutManager as LinearLayoutManager){
val visibleItemCount = childCount
val totalItemCount = itemCount
val firstVisibleItem = findFirstVisibleItemPosition()
if (loading && totalItemCount > previousTotal){
loading = false
previousTotal = totalItemCount
}
if(!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)){
onScrolledToEnd()
loading = true
}
}
}
})
}
And then use it like this:
youRecyclerView.addOnScrolledToEnd {
//What you want to do once the end is reached
}
This solution is based on Kushal Sharma's answer. However, this is a bit better because:
It uses onScrollStateChanged instead of onScroll. This is better because onScroll is called every time there is any sort of movement in the RecyclerView, whereas onScrollStateChanged is only called when the state of the RecyclerView is changed. Using onScrollStateChanged will save you CPU time and, as a consequence, battery.
Since this uses Extension Functions, this can be used in any RecyclerView you have. The client code is just 1 line.
Most answer are assuming the RecyclerView uses a LinearLayoutManager, or GridLayoutManager, or even StaggeredGridLayoutManager, or assuming that the scrolling is vertical or horyzontal, but no one has posted a completly generic answer.
Using the ViewHolder's adapter is clearly not a good solution. An adapter might have more than 1 RecyclerView using it. It "adapts" their contents. It should be the RecyclerView (which is the one class which is responsible of what is currently displayed to the user, and not the adapter which is responsible only to provide content to the RecyclerView) which must notify your system that more items are needed (to load).
Here is my solution, using nothing else than the abstracted classes of the RecyclerView (RecycerView.LayoutManager and RecycerView.Adapter):
/**
* Listener to callback when the last item of the adpater is visible to the user.
* It should then be the time to load more items.
**/
public abstract class LastItemListener extends RecyclerView.OnScrollListener {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// init
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (layoutManager.getChildCount() > 0) {
// Calculations..
int indexOfLastItemViewVisible = layoutManager.getChildCount() -1;
View lastItemViewVisible = layoutManager.getChildAt(indexOfLastItemViewVisible);
int adapterPosition = layoutManager.getPosition(lastItemViewVisible);
boolean isLastItemVisible = (adapterPosition == adapter.getItemCount() -1);
// check
if (isLastItemVisible)
onLastItemVisible(); // callback
}
}
/**
* Here you should load more items because user is seeing the last item of the list.
* Advice: you should add a bollean value to the class
* so that the method {#link #onLastItemVisible()} will be triggered only once
* and not every time the user touch the screen ;)
**/
public abstract void onLastItemVisible();
}
// --- Exemple of use ---
myRecyclerView.setOnScrollListener(new LastItemListener() {
public void onLastItemVisible() {
// start to load more items here.
}
}
Although the accepted answer works perfectly, the solution below uses addOnScrollListener since setOnScrollListener is deprecated, and reduces number of variables, and if conditions.
final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
feedsRecyclerView.setLayoutManager(layoutManager);
feedsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
if ((layoutManager.getChildCount() + layoutManager.findFirstVisibleItemPosition()) >= layoutManager.getItemCount()) {
Log.d("TAG", "End of list");
//loadMore();
}
}
}
});
This is how I do it, simple and short:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
if(!recyclerView.canScrollVertically(1) && dy != 0)
{
// Load more results here
}
}
});
Although there are so many answers to the question, I would like to share our experience of creating the endless list view. We have recently implemented custom Carousel LayoutManager that can work in the cycle by scrolling the list infinitely as well as up to a certain point. Here is a detailed description on GitHub.
I suggest you take a look at this article with short but valuable recommendations on creating custom LayoutManagers: http://cases.azoft.com/create-custom-layoutmanager-android/
OK, I did it by using the onBindViewHolder method of RecyclerView.Adapter.
Adapter:
public interface OnViewHolderListener {
void onRequestedLastItem();
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
...
if (position == getItemCount() - 1) onViewHolderListener.onRequestedLastItem();
}
Fragment (or Activity):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
contentView = inflater.inflate(R.layout.comments_list, container, false);
recyclerView = (RecyclerView) mContentView.findViewById(R.id.my_recycler_view);
adapter = new Adapter();
recyclerView.setAdapter(adapter);
...
adapter.setOnViewHolderListener(new Adapter.OnViewHolderListener() {
#Override
public void onRequestedLastItem() {
//TODO fetch new data from webservice
}
});
return contentView;
}
recyclerList.setOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx,int dy)
{
super.onScrolled(recyclerView, dx, dy);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView,int newState)
{
int totalItemCount = layoutManager.getItemCount();
int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
if (totalItemCount> 1)
{
if (lastVisibleItem >= totalItemCount - 1)
{
// End has been reached
// do something
}
}
}
});
I would try to extend used LayoutManager (e.g. LinearLayoutManager) and override scrollVerticallyBy() method. Firstly, I would call super first and then check returned integer value. If the value equals to 0 then a bottom or a top border is reached. Then I would use findLastVisibleItemPosition() method to find out which border is reached and load more data if needed. Just an idea.
In addition, you can even return your value from that method allowing overscroll and showing "loading" indicator.
I achieved an infinite scrolling type implementation using this logic in the onBindViewHolder method of my RecyclerView.Adapter class.
if (position == mItems.size() - 1 && mCurrentPage <= mTotalPageCount) {
if (mCurrentPage == mTotalPageCount) {
mLoadImagesListener.noMorePages();
} else {
int newPage = mCurrentPage + 1;
mLoadImagesListener.loadPage(newPage);
}
}
With this code when the RecyclerView gets to the last item, it increments the current page and callbacks on an interface which is responsible for loading more data from the api and adding the new results to the adapter.
I can post more complete example if this isn't clear?
For people who use StaggeredGridLayoutManager here is my implementation, it works for me.
private class ScrollListener extends RecyclerView.OnScrollListener {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
firstVivisibleItems = mLayoutManager.findFirstVisibleItemPositions(firstVivisibleItems);
if(!recyclerView.canScrollVertically(1) && firstVivisibleItems[0]!=0) {
loadMoreImages();
}
}
private boolean loadMoreImages(){
Log.d("myTag", "LAST-------HERE------");
return true;
}
}
There is an easy to use library for this named paginate . Supports both ListView and RecyclerView ( LinearLayout , GridLayout and StaggeredGridLayout).
Here is the link to the project on Github
My way to detect loading event is not to detect scrolling, but to listen whether the last view was attached. If the last view was attached, I regard it as timing to load more content.
class MyListener implements RecyclerView.OnChildAttachStateChangeListener {
RecyclerView mRecyclerView;
MyListener(RecyclerView view) {
mRecyclerView = view;
}
#Override
public void onChildViewAttachedToWindow(View view) {
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
RecyclerView.LayoutManager mgr = mRecyclerView.getLayoutManager();
int adapterPosition = mgr.getPosition(view);
if (adapterPosition == adapter.getItemCount() - 1) {
// last view was attached
loadMoreContent();
}
#Override
public void onChildViewDetachedFromWindow(View view) {}
}
Create an abstract class and extends RecyclerView.OnScrollListener
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold;
private int firstVisibleItem, visibleItemCount, totalItemCount;
private RecyclerView.LayoutManager layoutManager;
public EndlessRecyclerOnScrollListener(RecyclerView.LayoutManager layoutManager, int visibleThreshold) {
this.layoutManager = layoutManager; this.visibleThreshold = visibleThreshold;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
firstVisibleItem = ((LinearLayoutManager)layoutManager).findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
onLoadMore();
loading = true;
}
}
public abstract void onLoadMore();}
in activity (or fragment) add addOnScrollListener to recyclerView
LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager, 3) {
#Override
public void onLoadMore() {
//TODO
...
}
});
I have a pretty detailed example of how to paginate with a RecyclerView. At a high level, I have a set PAGE_SIZE , lets say 30. So I request 30 items and if I get 30 back then I request the next page. If I get less than 30 items I flag a variable to indicate that the last page has been reached and then I stop requesting for more pages. Check it out and let me know what you think.
https://medium.com/#etiennelawlor/pagination-with-recyclerview-1cb7e66a502b
Here my solution using AsyncListUtil, in the web says:
Note that this class uses a single thread to load the data, so it suitable to load data from secondary storage such as disk, but not from network.
but i am using odata to read the data and work fine.
I miss in my example data entities and network methods.
I include only the example adapter.
public class AsyncPlatoAdapter extends RecyclerView.Adapter {
private final AsyncPlatoListUtil mAsyncListUtil;
private final MainActivity mActivity;
private final RecyclerView mRecyclerView;
private final String mFilter;
private final String mOrderby;
private final String mExpand;
public AsyncPlatoAdapter(String filter, String orderby, String expand, RecyclerView recyclerView, MainActivity activity) {
mFilter = filter;
mOrderby = orderby;
mExpand = expand;
mRecyclerView = recyclerView;
mActivity = activity;
mAsyncListUtil = new AsyncPlatoListUtil();
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.plato_cardview, parent, false);
// Create a ViewHolder to find and hold these view references, and
// register OnClick with the view holder:
return new PlatoViewHolderAsync(itemView, this);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Plato item = mAsyncListUtil.getItem(position);
PlatoViewHolderAsync vh = (PlatoViewHolderAsync) holder;
if (item != null) {
Integer imagen_id = item.Imagen_Id.get();
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
vh.getImage().setVisibility(View.VISIBLE);
vh.getProgress().setVisibility(View.GONE);
String cacheName = null;
String urlString = null;
if (imagen_id != null) {
cacheName = String.format("imagenes/imagen/%d", imagen_id);
urlString = String.format("%s/menusapi/%s", MainActivity.ROOTPATH, cacheName);
}
ImageHelper.downloadBitmap(mActivity, vh.getImage(), vh.getProgress(), urlString, cacheName, position);
} else {
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
//show progress while loading.
vh.getImage().setVisibility(View.GONE);
vh.getProgress().setVisibility(View.VISIBLE);
}
}
#Override
public int getItemCount() {
return mAsyncListUtil.getItemCount();
}
public class AsyncPlatoListUtil extends AsyncListUtil<Plato> {
/**
* Creates an AsyncListUtil.
*/
public AsyncPlatoListUtil() {
super(Plato.class, //my data class
10, //page size
new DataCallback<Plato>() {
#Override
public int refreshData() {
//get count calling ../$count ... odata endpoint
return countPlatos(mFilter, mOrderby, mExpand, mActivity);
}
#Override
public void fillData(Plato[] data, int startPosition, int itemCount) {
//get items from odata endpoint using $skip and $top
Platos p = loadPlatos(mFilter, mOrderby, mExpand, startPosition, itemCount, mActivity);
for (int i = 0; i < Math.min(itemCount, p.value.size()); i++) {
data[i] = p.value.get(i);
}
}
}, new ViewCallback() {
#Override
public void getItemRangeInto(int[] outRange) {
//i use LinearLayoutManager in the RecyclerView
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
outRange[0] = layoutManager.findFirstVisibleItemPosition();
outRange[1] = layoutManager.findLastVisibleItemPosition();
}
#Override
public void onDataRefresh() {
mRecyclerView.getAdapter().notifyDataSetChanged();
}
#Override
public void onItemLoaded(int position) {
mRecyclerView.getAdapter().notifyItemChanged(position);
}
});
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
onRangeChanged();
}
});
}
}
}
if (layoutManager.findLastCompletelyVisibleItemPosition() ==
recyclerAdapter.getItemCount() - 1) {
//load more items.
}
Fair and simple.
This will work.
As #John T suggest. Just use code block below, really short, beauty and simple :D
public void loadMoreOnRecyclerView() {
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull #NotNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1) && dy != 0) {
//Load more items here
}
}
});
}
You can follow my Repo to understand the way that it work.
https://github.com/Nghien-Nghien/PokeAPI-Java/blob/0d8d69d348e068911b883f0ae7791d904cc75cb5/app/src/main/java/com/example/pokemonapi/MainActivity.java
Description info about app like this: https://github.com/skydoves/Pokedex#readme
There is a method public void setOnScrollListener (RecyclerView.OnScrollListener listener) in https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29. Use that
EDIT:
Override onScrollStateChanged method inside the onScrollListener and do this
boolean loadMore = firstVisibleItem + visibleItemCount >= totalItemCount;
//loading is used to see if its already loading, you have to manually manipulate this boolean variable
if (loadMore && !loading) {
//end of list reached
}
Check this every thing is explained in detail:
Pagination using RecyclerView From A to Z
mRecyclerView.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 = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
if (!mIsLoading && !mIsLastPage) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
})
loadMoreItems():
private void loadMoreItems() {
mAdapter.removeLoading();
//load data here from the server
// in case of success
mAdapter.addData(data);
// if there might be more data
mAdapter.addLoading();
}
in MyAdapter :
private boolean mIsLoadingFooterAdded = false;
public void addLoading() {
if (!mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = true;
mLineItemList.add(new LineItem());
notifyItemInserted(mLineItemList.size() - 1);
}
}
public void removeLoading() {
if (mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = false;
int position = mLineItemList.size() - 1;
LineItem item = mLineItemList.get(position);
if (item != null) {
mLineItemList.remove(position);
notifyItemRemoved(position);
}
}
}
public void addData(List<YourDataClass> data) {
for (int i = 0; i < data.size(); i++) {
YourDataClass yourDataObject = data.get(i);
mLineItemList.add(new LineItem(yourDataObject));
notifyItemInserted(mLineItemList.size() - 1);
}
}
None of these answers take into account if the list is too small or not.
Here's a piece of code I've been using that works on RecycleViews in both directions.
#Override
public boolean onTouchEvent(MotionEvent motionEvent) {
if (recyclerViewListener == null) {
return super.onTouchEvent(motionEvent);
}
/**
* If the list is too small to scroll.
*/
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}
return super.onTouchEvent(motionEvent);
}
public void setListener(RecyclerViewListener recycleViewListener) {
this.recyclerViewListener = recycleViewListener;
addOnScrollListener(new OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (recyclerViewListener == null) {
return;
}
recyclerViewListener.scrolling(dy);
if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}
});
}
I let you my aproximation. Works fine for me.
I hope it helps you.
/**
* Created by Daniel Pardo Ligorred on 03/03/2016.
*/
public abstract class BaseScrollListener extends RecyclerView.OnScrollListener {
protected RecyclerView.LayoutManager layoutManager;
public BaseScrollListener(RecyclerView.LayoutManager layoutManager) {
this.layoutManager = layoutManager;
this.init();
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
this.onScroll(recyclerView, this.getFirstVisibleItem(), this.layoutManager.getChildCount(), this.layoutManager.getItemCount(), dx, dy);
}
private int getFirstVisibleItem(){
if(this.layoutManager instanceof LinearLayoutManager){
return ((LinearLayoutManager) this.layoutManager).findFirstVisibleItemPosition();
} else if (this.layoutManager instanceof StaggeredGridLayoutManager){
int[] spanPositions = null; //Should be null -> StaggeredGridLayoutManager.findFirstVisibleItemPositions makes the work.
try{
return ((StaggeredGridLayoutManager) this.layoutManager).findFirstVisibleItemPositions(spanPositions)[0];
}catch (Exception ex){
// Do stuff...
}
}
return 0;
}
public abstract void init();
protected abstract void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy);
}
#kushal #abdulaziz
Why not use this logic instead?
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int totalItemCount, lastVisibleItemPosition;
if (dy > 0) {
totalItemCount = _layoutManager.getItemCount();
lastVisibleItemPosition = _layoutManager.findLastVisibleItemPosition();
if (!_isLastItem) {
if ((totalItemCount - 1) == lastVisibleItemPosition) {
LogUtil.e("end_of_list");
_isLastItem = true;
}
}
}
}
Try below:
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
/**
* Abstract Endless ScrollListener
*
*/
public abstract class EndlessScrollListener extends
RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 10;
// The current offset index of data you have loaded
private int currentPage = 1;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// The total number of items in the data set after the last load
private int previousTotal = 0;
private int firstVisibleItem;
private int visibleItemCount;
private int totalItemCount;
private LayoutManager layoutManager;
public EndlessScrollListener(LayoutManager layoutManager) {
validateLayoutManager(layoutManager);
this.layoutManager = layoutManager;
}
public EndlessScrollListener(int visibleThreshold,
LayoutManager layoutManager, int startPage) {
validateLayoutManager(layoutManager);
this.visibleThreshold = visibleThreshold;
this.layoutManager = layoutManager;
this.currentPage = startPage;
}
private void validateLayoutManager(LayoutManager layoutManager)
throws IllegalArgumentException {
if (null == layoutManager
|| !(layoutManager instanceof GridLayoutManager)
|| !(layoutManager instanceof LinearLayoutManager)) {
throw new IllegalArgumentException(
"LayoutManager must be of type GridLayoutManager or LinearLayoutManager.");
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
if (layoutManager instanceof GridLayoutManager) {
firstVisibleItem = ((GridLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
} else if (layoutManager instanceof LinearLayoutManager) {
firstVisibleItem = ((LinearLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
}
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading
&& (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
// End has been reached do something
currentPage++;
onLoadMore(currentPage);
loading = true;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page);
}
I have created LoadMoreRecyclerView using Abdulaziz Noor Answer
LoadMoreRecyclerView
public class LoadMoreRecyclerView extends RecyclerView {
private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
//WrapperLinearLayout is for handling crash in RecyclerView
private WrapperLinearLayout mLayoutManager;
private Context mContext;
private OnLoadMoreListener onLoadMoreListener;
public LoadMoreRecyclerView(Context context) {
super(context);
mContext = context;
init();
}
public LoadMoreRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public LoadMoreRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}
private void init(){
mLayoutManager = new WrapperLinearLayout(mContext,LinearLayoutManager.VERTICAL,false);
this.setLayoutManager(mLayoutManager);
this.setItemAnimator(new DefaultItemAnimator());
this.setHasFixedSize(true);
}
#Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
if(dy > 0) //check for scroll down
{
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading)
{
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount)
{
loading = false;
Log.v("...", "Call Load More !");
if(onLoadMoreListener != null){
onLoadMoreListener.onLoadMore();
}
//Do pagination.. i.e. fetch new data
}
}
}
}
#Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
}
public void onLoadMoreCompleted(){
loading = true;
}
public void setMoreLoading(boolean moreLoading){
loading = moreLoading;
}
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
}
WrapperLinearLayout
public class WrapperLinearLayout extends LinearLayoutManager
{
public WrapperLinearLayout(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("probe", "meet a IOOBE in RecyclerView");
}
}
}
//Add it in xml like
<your.package.LoadMoreRecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</your.package.LoadMoreRecyclerView>
OnCreate or onViewCreated
mLoadMoreRecyclerView = (LoadMoreRecyclerView) view.findViewById(R.id.recycler_view);
mLoadMoreRecyclerView.setOnLoadMoreListener(new OnLoadMoreListener() {
#Override
public void onLoadMore() {
callYourService(StartIndex);
}
});
callYourService
private void callYourService(){
//callyour Service and get response in any List
List<AnyModelClass> newDataFromServer = getDataFromServerService();
//Enable Load More
mLoadMoreRecyclerView.onLoadMoreCompleted();
if(newDataFromServer != null && newDataFromServer.size() > 0){
StartIndex += newDataFromServer.size();
if (newDataFromServer.size() < Integer.valueOf(MAX_ROWS)) {
//StopLoading..
mLoadMoreRecyclerView.setMoreLoading(false);
}
}
else{
mLoadMoreRecyclerView.setMoreLoading(false);
mAdapter.notifyDataSetChanged();
}
}

Recyclerview indicates that reaches the last item

I have a recycleView that I need to observe when the last item is reached but I have notice the it always indicate that I reached the last item even if I haven't scrolled yet.
My code for setting up the recycler:
newsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
newsRecyclerView.setFocusable(false);
newsRecyclerView.setNestedScrollingEnabled(false);
newsAdapter = new NewsAdapter(getContext(), newsDetails, categoryNumber);
newsRecyclerView.setAdapter(newsAdapter);
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
My xml code is:
<android.support.v7.widget.RecyclerView
android:id="#+id/news_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/news_top_stories_title_text_view" />
This is my code that I put in my Util, and use anywhere.
Util.setRecyclerViewLastPositionListner(rv, linearLayoutManager , new UtilitiesV2.OnLastPositionReached() {
#Override
public void onReached() {
// last position reached
}
});
Put this in Util.
private boolean userScrolled = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
public interface OnLastPositionReached {
void onReached();
}
public void setRecyclerViewLastPositionListner(RecyclerView rvBooksMockTest, final LinearLayoutManager mLayoutManager, final OnLastPositionReached onLastPositionReached) {
rvBooksMockTest.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
userScrolled = true;
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// Here get the child count, item count and visibleitems
// from layout manager
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
// Now check if userScrolled is true and also check if
// the item is end then update recycler view and set
// userScrolled to false
if (userScrolled && (visibleItemCount + pastVisiblesItems) == totalItemCount) {
userScrolled = false;
if (onLastPositionReached != null) onLastPositionReached.onReached();
}
}
});
}
Update
According to your requirement, here is NestedScrollView bottom reach listener.
nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (nestedScrollView != null) {
if (nestedScrollView.getChildAt(0).getBottom() <= (nestedScrollView.getHeight() + nestedScrollView.getScrollY())) {
//scroll view is at bottom
} else {
//scroll view is not at bottom
}
}
}
});
Thanks to Khemraj who give the tip for solution
because I have a recyclerview inside NestedScrollView and coordinator layout as parent for them
I have solved my problem like this:
public Disposable observeNestedScroll(LoadMoreListener listener) {
return RxNestedScrollView.scrollChangeEvents(nestedScrollView)
.subscribe(
viewScrollChangeEvent -> {
NestedScrollView nestedScrollView =(NestedScrollView) viewScrollChangeEvent.view();
if(nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1) != null) {
if ((viewScrollChangeEvent.scrollY() >= (nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1).getMeasuredHeight() - nestedScrollView.getMeasuredHeight())) &&
viewScrollChangeEvent.scrollY() > viewScrollChangeEvent.oldScrollY()) {
listener.onLoadMore();
}
}
});
}
I've seen to many responses for this question and I stand that all of them don't give accurate behavior as an outcome. However if you follow this approach I'm positive you'll get the best behavior.
rvCategories is your RecyclerView
categoriesList is the list passed to your adapter
binding.rvCategories.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val position = (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
if (position + 1 == categoriesList.size) {
// END OF RECYCLERVIEW IS REACHED
} else {
// END OF RECYCLERVIEW IS NOT REACHED
}
}
})

How to sync scrolling first-positions of 2 RecyclerViews?

Background
I have 2 RecyclerView instances. One is horizontal, and the second is vertical.
They both show the same data and have the same amount of items, but in different ways, and the cells are not necessary equal in size through each of them .
I wish that scrolling in one will sync with the other, so that the first item that's shown on one, will always be shown on the other (as the first).
The problem
Even though I've succeeded making them sync (I just choose which one is the "master", to control the scrolling of the other), the direction of the scrolling seems to affect the way it works.
Suppose for a moment the cells have equal heeight:
If I scroll up/left, it works as I intended, more or less:
However, if I scroll down/right, it does let the other RecyclerView show the first item of the other, but usually not as the first item:
Note: on the above screenshots, I've scrolled in the bottom RecyclerView, but a similar result will be with the top one.
The situation gets much worse if, as I wrote, the cells have different sizes:
What I've tried
I tried to use other ways of scrolling and going to other positions, but all attempts fail.
Using smoothScrollToPosition made things even worse (though it does seem nicer), because if I fling, at some point the other RecyclerView takes control of the one I've interacted with.
I think I should use the direction of the scrolling, together with the currently shown items on the other RecyclerView.
Here's the current (sample) code. Note that in the real code, the cells might not have equal sizes (some are tall, some are short, etc...). One of the lines in the code makes the cells have different height.
activity_main.xml
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/topReccyclerView" android:layout_width="0dp" android:layout_height="100dp"
android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp"
android:orientation="horizontal" app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" tools:listitem="#layout/horizontal_cell"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/bottomRecyclerView" android:layout_width="0dp" android:layout_height="0dp"
android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="#+id/topReccyclerView"
tools:listitem="#layout/horizontal_cell"/>
</android.support.constraint.ConstraintLayout>
horizontal_cell.xml
<TextView
android:id="#+id/textView" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="100dp" android:layout_height="100dp"
android:gravity="center" tools:text="#tools:sample/lorem"/>
vertical_cell.xml
<TextView
android:id="#+id/textView" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="50dp"
android:gravity="center" tools:text="#tools:sample/lorem"/>
MainActivity
class MainActivity : AppCompatActivity() {
var masterView: View? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val inflater = LayoutInflater.from(this)
topReccyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if(position%2==0) 0xffff0000.toInt() else 0xff00ff00.toInt())
}
override fun getItemCount(): Int {
return 100
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.horizontal_cell, parent, false)) {}
}
}
bottomRecyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val baseHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if(position%2==0) 0xffff0000.toInt() else 0xff00ff00.toInt())
// this makes the heights of the cells different from one another:
holder.itemView.layoutParams.height = baseHeight + (if (position % 3 == 0) 0 else baseHeight / (position % 3))
}
override fun getItemCount(): Int {
return 100
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.vertical_cell, parent, false)) {}
}
}
LinearSnapHelper().attachToRecyclerView(topReccyclerView)
LinearSnapHelper().attachToRecyclerView(bottomRecyclerView)
topReccyclerView.addOnScrollListener(OnScrollListener(topReccyclerView, bottomRecyclerView))
bottomRecyclerView.addOnScrollListener(OnScrollListener(bottomRecyclerView, topReccyclerView))
}
inner class OnScrollListener(private val thisRecyclerView: RecyclerView, private val otherRecyclerView: RecyclerView) : RecyclerView.OnScrollListener() {
var lastItemPos: Int = Int.MIN_VALUE
val thisRecyclerViewId = resources.getResourceEntryName(thisRecyclerView.id)
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
Log.d("AppLog", "onScrollStateChanged:$thisRecyclerViewId $newState")
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> if (masterView == null) {
Log.d("AppLog", "setting $thisRecyclerViewId to be master")
masterView = thisRecyclerView
}
RecyclerView.SCROLL_STATE_IDLE -> if (masterView == thisRecyclerView) {
Log.d("AppLog", "resetting $thisRecyclerViewId from being master")
masterView = null
lastItemPos = Int.MIN_VALUE
}
}
}
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if ((dx == 0 && dy == 0) || (masterView != null && masterView != thisRecyclerView))
return
// Log.d("AppLog", "onScrolled:$thisRecyclerView $dx-$dy")
val currentItem = (thisRecyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
if (lastItemPos == currentItem)
return
lastItemPos = currentItem
otherRecyclerView.scrollToPosition(currentItem)
// otherRecyclerView.smoothScrollToPosition(currentItem)
Log.d("AppLog", "currentItem:" + currentItem)
}
}
}
The questions
How do I let the other RecycerView to always have the first item the same as the currently controlled one?
How to modify the code to support smooth scrolling, without causing the issue of suddenly having the other RecyclerView being the one that controls ?
EDIT: after updating the sample code here with having different sizes of cells (because originally that's closer to the issue I have, as I described before), I noticed that the snapping doesn't work well.
That's why I chose to use this library to snap it correctly:
https://github.com/DevExchanges/SnappingRecyclerview
So instead of LinearSnapHelper, I use 'GravitySnapHelper'. Seems to work better, but still have the syncing issues, and touching while it scrolls.
EDIT:
I've finally fixed all syncing issues, and it works fine even if the cells have different sizes.
Still has some issues:
If you fling on one RecyclerView, and then touch the other one, it has very weird behavior of scrolling. Might scroll way more than it should.
The scrolling isn't smooth (when syncing and when flinging), so it doesn't look well.
Sadly, because of the snapping (which I actually might need only for the top RecyclerView), it causes another issue: the bottom RecyclerView might show the last item partially (screenshot with 100 items), and I can't scroll more to show it fully :
I don't even think that the bottom RecyclerView should be snapping, unless the top one was touched. Sadly this is all I got so far, that has no syncing issues.
Here's the new code, after all the fixes I've found:
class MainActivity : AppCompatActivity() {
var masterView: View? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val inflater = LayoutInflater.from(this)
topReccyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if (position % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
}
override fun getItemCount(): Int = 1000
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.horizontal_cell, parent, false)) {}
}
}
bottomRecyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val baseHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if (position % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
holder.itemView.layoutParams.height = baseHeight + (if (position % 3 == 0) 0 else baseHeight / (position % 3))
}
override fun getItemCount(): Int = 1000
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.vertical_cell, parent, false)) {}
}
}
// GravitySnapHelper is available from : https://github.com/DevExchanges/SnappingRecyclerview
GravitySnapHelper(Gravity.START).attachToRecyclerView(topReccyclerView)
GravitySnapHelper(Gravity.TOP).attachToRecyclerView(bottomRecyclerView)
topReccyclerView.addOnScrollListener(OnScrollListener(topReccyclerView, bottomRecyclerView))
bottomRecyclerView.addOnScrollListener(OnScrollListener(bottomRecyclerView, topReccyclerView))
}
inner class OnScrollListener(private val thisRecyclerView: RecyclerView, private val otherRecyclerView: RecyclerView) : RecyclerView.OnScrollListener() {
var lastItemPos: Int = Int.MIN_VALUE
val thisRecyclerViewId = resources.getResourceEntryName(thisRecyclerView.id)
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> if (masterView == null) {
masterView = thisRecyclerView
}
RecyclerView.SCROLL_STATE_IDLE -> if (masterView == thisRecyclerView) {
masterView = null
lastItemPos = Int.MIN_VALUE
}
}
}
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dx == 0 && dy == 0 || masterView !== null && masterView !== thisRecyclerView) {
return
}
val otherLayoutManager = otherRecyclerView.layoutManager as LinearLayoutManager
val thisLayoutManager = thisRecyclerView.layoutManager as LinearLayoutManager
val currentItem = thisLayoutManager.findFirstCompletelyVisibleItemPosition()
if (lastItemPos == currentItem) {
return
}
lastItemPos = currentItem
otherLayoutManager.scrollToPositionWithOffset(currentItem, 0)
}
}
}
Combining the two RecyclerViews, there are four cases of movement:
a. Scrolling the horizontal recycler to the left
b. Scrolling it to the right
c. Scrolling the vertical recycler to the top
d. Scrolling it to the bottom
Cases a and c don't need to be taken care of since they work out of the box. For cases b and d you need to do two things:
Know which recycler you are in (vertical or horizontal) and which direction the scroll went (up or down resp. left or right) and
calculate an offset (of list items) from the number of visible items in otherRecyclerView (if the screen is bigger the offset needs to be bigger, too).
Figuring this out was a bit fiddly, but the result is pretty straight forward.
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
if (masterView == otherRecyclerView) {
thisRecyclerView.stopScroll();
otherRecyclerView.stopScroll();
syncScroll(1, 1);
}
masterView = thisRecyclerView;
} else if (newState == RecyclerView.SCROLL_STATE_IDLE && masterView == thisRecyclerView) {
masterView = null;
}
}
#Override
public void onScrolled(RecyclerView recyclerview, int dx, int dy) {
super.onScrolled(recyclerview, dx, dy);
if ((dx == 0 && dy == 0) || (masterView != null && masterView != thisRecyclerView)) {
return;
}
syncScroll(dx, dy);
}
void syncScroll(int dx, int dy) {
LinearLayoutManager otherLayoutManager = (LinearLayoutManager) otherRecyclerView.getLayoutManager();
LinearLayoutManager thisLayoutManager = (LinearLayoutManager) thisRecyclerView.getLayoutManager();
int offset = 0;
if ((thisLayoutManager.getOrientation() == HORIZONTAL && dx > 0) || (thisLayoutManager.getOrientation() == VERTICAL && dy > 0)) {
// scrolling horizontal recycler to left or vertical recycler to bottom
offset = otherLayoutManager.findLastCompletelyVisibleItemPosition() - otherLayoutManager.findFirstCompletelyVisibleItemPosition();
}
int currentItem = thisLayoutManager.findFirstCompletelyVisibleItemPosition();
otherLayoutManager.scrollToPositionWithOffset(currentItem, offset);
}
Of course you could combine the two if clauses since the bodies are the same. For the sake of readability, I thought it is good to keep them apart.
The second problem was syncing when the respective "other" recycler was touched while the "first" recycler was still scrolling. Here the following code (included above) is relevant:
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
if (masterView == otherRecyclerView) {
thisRecyclerView.stopScroll();
otherRecyclerView.stopScroll();
syncScroll(1, 1);
}
masterView = thisRecyclerView;
}
newState equals SCROLL_STATE_DRAGGING when the recycler is touched and dragged a little bit. So if this is a touch (& drag) after a touch on the respective "other" recycler, the second condition (masterView == otherRecyclerview) is true. Both recyclers are stopped then and the "other" recycler is synced with "this" one.
1-) Layout manager
The current smoothScrollToPosition does not take the element to the top. So let's write a new layout manager. And let's override this layout manager's smoothScrollToPosition.
public class TopLinearLayoutManager extends LinearLayoutManager
{
public TopLinearLayoutManager(Context context, int orientation)
{
//orientation : vertical or horizontal
super(context, orientation, false);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position)
{
RecyclerView.SmoothScroller smoothScroller = new TopSmoothScroller(recyclerView.getContext());
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
private class TopSmoothScroller extends LinearSmoothScroller
{
TopSmoothScroller(Context context)
{
super(context);
}
#Override
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference)
{
return (boxStart - viewStart);
}
}
}
2-) Setup
//horizontal one
RecyclerView rvMario = (RecyclerView) findViewById(R.id.rvMario);
//vertical one
RecyclerView rvLuigi = (RecyclerView) findViewById(R.id.rvLuigi);
final LinearLayoutManager managerMario = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL, false);
rvMario.setLayoutManager(managerMario);
ItemMarioAdapter adapterMario = new ItemMarioAdapter(itemList);
rvMario.setAdapter(adapterMario);
//Snap to start by using Ruben Sousa's RecyclerViewSnap
SnapHelper snapHelper = new GravitySnapHelper(Gravity.START);
snapHelper.attachToRecyclerView(rvMario);
final TopLinearLayoutManager managerLuigi = new TopLinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL);
rvLuigi.setLayoutManager(managerLuigi);
ItemLuigiAdapter adapterLuigi = new ItemLuigiAdapter(itemList);
rvLuigi.setAdapter(adapterLuigi);
3-) Scroll listener
rvMario.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
super.onScrolled(recyclerView, dx, dy);
//get firstCompleteleyVisibleItemPosition
int firstCompleteleyVisibleItemPosition = managerMario.findFirstCompletelyVisibleItemPosition();
if (firstCompleteleyVisibleItemPosition >= 0)
{
//vertical one, smooth scroll to position
rvLuigi.smoothScrollToPosition(firstCompleteleyVisibleItemPosition);
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState)
{
super.onScrollStateChanged(recyclerView, newState);
}
});
4-) Output
Building on Burak's TopLinearLayoutManager, but correcting the logic of the OnScrollListener we finally get working smoothscrolling and correct snapping (of the horizontal RecyclerView).
public class MainActivity extends AppCompatActivity {
View masterView = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
final LayoutInflater inflater = LayoutInflater.from(this);
final RecyclerView topRecyclerView = findViewById(R.id.topReccyclerView);
RecyclerView.Adapter adapterTop = new RecyclerView.Adapter<RecyclerView.ViewHolder>() {
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.horizontal_cell, parent, false));
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((TextView) holder.itemView).setText(String.valueOf(position));
holder.itemView.setBackgroundColor(position % 2 == 0 ? Integer.valueOf(0xffff0000) : Integer.valueOf(0xff00ff00));
}
#Override
public int getItemCount() {
return 100;
}
class ViewHolder extends RecyclerView.ViewHolder {
final TextView textView;
ViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
}
}
};
topRecyclerView.setAdapter(adapterTop);
final RecyclerView bottomRecyclerView = findViewById(R.id.bottomRecyclerView);
RecyclerView.Adapter adapterBottom = new RecyclerView.Adapter() {
int baseHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, getResources().getDisplayMetrics());
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.vertical_cell, parent, false));
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((TextView) holder.itemView).setText(String.valueOf(position));
holder.itemView.setBackgroundColor((position % 2 == 0) ? Integer.valueOf(0xffff0000) : Integer.valueOf(0xff00ff00));
holder.itemView.getLayoutParams().height = baseHeight + (position % 3 == 0 ? 0 : baseHeight / (position % 3));
}
#Override
public int getItemCount() {
return 100;
}
class ViewHolder extends RecyclerView.ViewHolder {
final TextView textView;
ViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
}
}
};
bottomRecyclerView.setAdapter(adapterBottom);
TopLinearLayoutManager topLayoutManager = new TopLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL);
topRecyclerView.setLayoutManager(topLayoutManager);
TopLinearLayoutManager bottomLayoutManager = new TopLinearLayoutManager(this, LinearLayoutManager.VERTICAL);
bottomRecyclerView.setLayoutManager(bottomLayoutManager);
final OnScrollListener topOnScrollListener = new OnScrollListener(topRecyclerView, bottomRecyclerView);
final OnScrollListener bottomOnScrollListener = new OnScrollListener(bottomRecyclerView, topRecyclerView);
topRecyclerView.addOnScrollListener(topOnScrollListener);
bottomRecyclerView.addOnScrollListener(bottomOnScrollListener);
GravitySnapHelper snapHelperTop = new GravitySnapHelper(Gravity.START);
snapHelperTop.attachToRecyclerView(topRecyclerView);
}
class OnScrollListener extends RecyclerView.OnScrollListener {
private RecyclerView thisRecyclerView;
private RecyclerView otherRecyclerView;
int lastItemPos = Integer.MIN_VALUE;
OnScrollListener(RecyclerView thisRecyclerView, RecyclerView otherRecyclerView) {
this.thisRecyclerView = thisRecyclerView;
this.otherRecyclerView = otherRecyclerView;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
masterView = thisRecyclerView;
} else if (newState == RecyclerView.SCROLL_STATE_IDLE && masterView == thisRecyclerView) {
masterView = null;
lastItemPos = Integer.MIN_VALUE;
}
}
#Override
public void onScrolled(RecyclerView recyclerview, int dx, int dy) {
super.onScrolled(recyclerview, dx, dy);
if ((dx == 0 && dy == 0) || (masterView != thisRecyclerView)) {
return;
}
int currentItem = ((TopLinearLayoutManager) thisRecyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
if (lastItemPos == currentItem) {
return;
}
lastItemPos = currentItem;
otherRecyclerView.getLayoutManager().smoothScrollToPosition(otherRecyclerView, null, currentItem);
}
}
}
Another simple solution working fine in my device
Variable
RecyclerView horizontalRecyclerView, verticalRecyclerView;
LinearLayoutManager horizontalLayoutManager, verticalLayoutManager;
ArrayList<String> arrayList = new ArrayList<>();
ArrayList<String> arrayList2 = new ArrayList<>();
RecyclerView code
horizontalRecyclerView = findViewById(R.id.horizontalRc);
verticalRecyclerView = findViewById(R.id.verticalRc);
horizontalRecyclerView.setHasFixedSize(true);
verticalRecyclerView.setHasFixedSize(true);
horizontalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
verticalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
horizontalRecyclerView.setLayoutManager(horizontalLayoutManager);
verticalRecyclerView.setLayoutManager(verticalLayoutManager);
for (int i = 0; i < 50; i++) {
arrayList.add("" + i);
arrayList2.add("" + i);
}
MyDataAdapter horizontalAdapter = new MyDataAdapter(this, arrayList);
MyDataAdapter verticalAdapter = new MyDataAdapter(this, arrayList2);
horizontalRecyclerView.setAdapter(horizontalAdapter);
verticalRecyclerView.setAdapter(verticalAdapter);
logic inside RecyclerView.addOnScrollListener
horizontalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int pos = horizontalLayoutManager.findFirstCompletelyVisibleItemPosition();
verticalLayoutManager.scrollToPositionWithOffset(pos, 20);
}
});
verticalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int pos = verticalLayoutManager.findFirstCompletelyVisibleItemPosition();
horizontalLayoutManager.scrollToPositionWithOffset(pos, 20);
}
});
Hope it help some one
WHOLE Code
public class Main4Activity extends AppCompatActivity {
RecyclerView horizontalRecyclerView, verticalRecyclerView;
LinearLayoutManager horizontalLayoutManager, verticalLayoutManager;
ArrayList<String> arrayList = new ArrayList<>();
ArrayList<String> arrayList2 = new ArrayList<>();
boolean isVertical = true, isHorizontal = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main4);
horizontalRecyclerView = findViewById(R.id.horizontalRc);
verticalRecyclerView = findViewById(R.id.verticalRc);
horizontalRecyclerView.setHasFixedSize(true);
verticalRecyclerView.setHasFixedSize(true);
horizontalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
verticalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
horizontalRecyclerView.setLayoutManager(horizontalLayoutManager);
verticalRecyclerView.setLayoutManager(verticalLayoutManager);
for (int i = 0; i < 50; i++) {
arrayList.add("" + i);
arrayList2.add("" + i);
}
MyDataAdapter horizontalAdapter = new MyDataAdapter(this, arrayList);
MyDataAdapter verticalAdapter = new MyDataAdapter(this, arrayList2);
horizontalRecyclerView.setAdapter(horizontalAdapter);
verticalRecyclerView.setAdapter(verticalAdapter);
horizontalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int pos = horizontalLayoutManager.findFirstCompletelyVisibleItemPosition();
verticalLayoutManager.scrollToPositionWithOffset(pos, 20);
}
});
verticalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int pos = verticalLayoutManager.findFirstCompletelyVisibleItemPosition();
horizontalLayoutManager.scrollToPositionWithOffset(pos, 20);
}
});
/* horizontalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int pos = horizontalLayoutManager.findFirstCompletelyVisibleItemPosition();
verticalLayoutManager.scrollToPositionWithOffset(pos, 20);
*//*if (isHorizontal) {
int pos = horizontalLayoutManager.findFirstCompletelyVisibleItemPosition();
verticalLayoutManager.scrollToPositionWithOffset(pos, 20);
Log.e("isHorizontal", "TRUE");
isVertical = false;
} else {
isHorizontal = true;
}*//*
}
*//* #Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
isVertical = true;
}*//*
});
verticalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
*//* #Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
isHorizontal = true;
}
*//*
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int pos = verticalLayoutManager.findFirstCompletelyVisibleItemPosition();
horizontalLayoutManager.scrollToPositionWithOffset(pos, 20);
*//* if (isVertical) {
int pos = verticalLayoutManager.findFirstCompletelyVisibleItemPosition();
horizontalLayoutManager.scrollToPositionWithOffset(pos, 20);
Log.e("isVertical", "TRUE");
isHorizontal = false;
} else {
isVertical = true;
}*//*
}
});*/
}
}
Adapter code
public class MyDataAdapter extends RecyclerView.Adapter<MyDataAdapter.ViewHolder> {
Context context;
ArrayList<String> arrayList;
public MyDataAdapter(Context context, ArrayList<String> arrayList) {
this.context = context;
this.arrayList = arrayList;
}
#Override
public MyDataAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.temp, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(MyDataAdapter.ViewHolder holder, int position) {
if (position % 2 == 0) {
holder.tvNumber.setBackgroundResource(R.color.colorGreen);
} else {
holder.tvNumber.setBackgroundResource(R.color.colorRed);
}
holder.tvNumber.setText(arrayList.get(position));
}
#Override
public int getItemCount() {
return arrayList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView tvNumber;
public ViewHolder(View itemView) {
super(itemView);
tvNumber = itemView.findViewById(R.id.tvNumber);
}
}
}
Activity layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/horizontalRc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<android.support.v7.widget.RecyclerView
android:id="#+id/verticalRc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:visibility="gone" />
</LinearLayout>
temp Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="40dp">
<TextView
android:id="#+id/tvNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="50dp" />
</LinearLayout>
COLOR
<color name="colorGreen">#307832</color>
<color name="colorRed">#ff4c4c</color>

Scrolling SwipeRefreshLayout with RecyclerView Refresh anywhere in android 2.2

I have problem with my layout, I created SwipeRefreshLayout with RecyclerView inside.
in android 4.2.2+ all is working good, but in andorid 2.3.4 I cant to scroll up because in any place in the RecyclerView it will refresh, and I must to scroll down and then scroll up.
This is my code:
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/forum_swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/LVP"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center" />
</android.support.v4.widget.SwipeRefreshLayout>
I found this issue:https://code.google.com/p/android/issues/detail?id=78191 but no a solution.
Any idea how to fix it?
override RecyclerView's method OnScrollStateChanged
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
// TODO Auto-generated method stub
//super.onScrollStateChanged(recyclerView, newState);
try {
int firstPos = mLayoutManager.findFirstCompletelyVisibleItemPosition();
if (firstPos > 0) {
mSwipeRefreshLayout.setEnabled(false);
} else {
mSwipeRefreshLayout.setEnabled(true);
if(mRecyclerView.getScrollState() == 1)
if(mSwipeRefreshLayout.isRefreshing())
mRecyclerView.stopScroll();
}
}catch(Exception e) {
Log.e(TAG, "Scroll Error : "+e.getLocalizedMessage());
}
}
Check if Swipe Refresh is Refreshing and try to Scroll up then you got error, so when swipe refresh is going on and i try do this mRecyclerView.stopScroll();
I fixed the scroll up issue using the following code :
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager manager = ((LinearLayoutManager)recyclerView.getLayoutManager());
boolean enabled =manager.findFirstCompletelyVisibleItemPosition() == 0;
pullToRefreshLayout.setEnabled(enabled);
}
};
Then you need to use setOnScrollListener or addOnScrollListener depending if you have one or more listeners.
Unfortunately, this is a known issue and will be fixed in a future release.
https://code.google.com/p/android/issues/detail?id=78191
Meanwhile, if you need urgent fix, override canChildScrollUp in SwipeRefreshLayout.java and call recyclerView.canScrollVertically(mTarget, -1). Because canScrollVertically was added after gingerbread, you'll also need to copy that method and implement in recyclerview.
Alternatively, if you are using LinearLayoutManager, you can call findFirstCompletelyVisibleItemPosition.
Sorry for the inconvenience.
You can disable/enable the refresh layout based on recyclerview's scroll ability
public class RecyclerSwipeRefreshHelper extends RecyclerView.OnScrollListener{
private static final int DIRECTION_UP = -1;
private final SwipeRefreshLayout refreshLayout;
public RecyclerSwipeRefreshHelper(
SwipeRefreshLayout refreshLayout) {
this.refreshLayout = refreshLayout;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
refreshLayout.setEnabled((recyclerView.canScrollVertically(DIRECTION_UP)));
}
}
You can override the method canChildScrollUp() in SwipeRefreshLayout like this:
public boolean canChildScrollUp() {
if (mTarget instanceof RecyclerView) {
final RecyclerView recyclerView = (RecyclerView) mTarget;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
int position = ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
return position != 0;
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] positions = ((StaggeredGridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPositions(null);
for (int i = 0; i < positions.length; i++) {
if (positions[i] == 0) {
return false;
}
}
}
return true;
} else if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}
Based on #wrecker answer (https://stackoverflow.com/a/32318447/7508302).
In Kotlin we can use extension method. So:
class RecyclerViewSwipeToRefresh(private val refreshLayout: SwipeToRefreshLayout) : RecyclerView.OnScrollListener() {
companion object {
private const val DIRECTION_UP = -1
}
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
refreshLayout.isEnabled = !(recyclerView?.canScrollVertically(DIRECTION_UP) ?: return)
}
}
And let's add extension method to RecyclerView to easly apply this fix to RV.
fun RecyclerView.fixSwipeToRefresh(refreshLayout: SwipeRefreshLayout): RecyclerViewSwipeToRefresh {
return RecyclerViewSwipeToRefresh(refreshLayout).also {
this.addOnScrollListener(it)
}
}
Now, we can fix recyclerView using:
recycler_view.apply {
...
fixSwipeToRefresh(swipe_container)
...
}
Following code is working for me, please ensure that it is placed below the binding.refreshDiscoverList.setOnRefreshListener{} method.
binding.swipeToRefreshLayout.setOnChildScrollUpCallback(object : SwipeRefreshLayout.OnChildScrollUpCallback {
override fun canChildScrollUp(parent: SwipeRefreshLayout, child: View?): Boolean {
if (binding.rvDiscover != null) {
return binding.recyclerView.canScrollVertically(-1)
}
return false
}
})

How to implement endless list with RecyclerView?

I would like to change ListView to RecyclerView. I want to use the onScroll of the OnScrollListener in RecyclerView to determine if a user scrolled to the end of the list.
How do I know if a user scrolls to the end of the list so that I can fetch new data from a REST service?
Thanks to #Kushal and this is how I implemented it
private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy > 0) { //check for scroll down
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
loading = false;
Log.v("...", "Last Item Wow !");
// Do pagination.. i.e. fetch new data
loading = true;
}
}
}
}
});
Don't forget to add
LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Make these variables.
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
Set on Scroll for recycler view.
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
// End has been reached
Log.i("Yaeye!", "end called");
// Do something
loading = true;
}
}
});
Note : Make sure you are using LinearLayoutManager as layout manager for RecyclerView.
LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
and for a grid
GridLayoutManager mLayoutManager;
mLayoutManager = new GridLayoutManager(getActivity(), spanCount);
mRecyclerView.setLayoutManager(mLayoutManager);
Have fun with your endless scrolls !! ^.^
Update : mRecyclerView.setOnScrollListener() is deprecated just replace with mRecyclerView.addOnScrollListener() and the warning will be gone! You can read more from this SO question.
Since Android now officially support Kotlin, here is an update for the same -
Make OnScrollListener
class OnScrollListener(val layoutManager: LinearLayoutManager, val adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>, val dataList: MutableList<Int>) : RecyclerView.OnScrollListener() {
var previousTotal = 0
var loading = true
val visibleThreshold = 10
var firstVisibleItem = 0
var visibleItemCount = 0
var totalItemCount = 0
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
visibleItemCount = recyclerView.childCount
totalItemCount = layoutManager.itemCount
firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
if (loading) {
if (totalItemCount > previousTotal) {
loading = false
previousTotal = totalItemCount
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
val initialSize = dataList.size
updateDataList(dataList)
val updatedSize = dataList.size
recyclerView.post { adapter.notifyItemRangeInserted(initialSize, updatedSize) }
loading = true
}
}
}
and add it to your RecyclerView like this
recyclerView.addOnScrollListener(OnScrollListener(layoutManager, adapter, dataList))
For a full code example, feel free to refer this Github repo.
For those who only want to get notified when the last item is totally shown, you can use View.canScrollVertically().
Here is my implementation:
public abstract class OnVerticalScrollListener
extends RecyclerView.OnScrollListener {
#Override
public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (!recyclerView.canScrollVertically(-1)) {
onScrolledToTop();
} else if (!recyclerView.canScrollVertically(1)) {
onScrolledToBottom();
} else if (dy < 0) {
onScrolledUp();
} else if (dy > 0) {
onScrolledDown();
}
}
public void onScrolledUp() {}
public void onScrolledDown() {}
public void onScrolledToTop() {}
public void onScrolledToBottom() {}
}
Note: You can use recyclerView.getLayoutManager().canScrollVertically() if you want to support API < 14.
Here is another approach. It will work with any layout manager.
Make Adapter class abstract
Then create an abstract method in adapter class (eg. load())
In onBindViewHolder check the position if last and call load()
Override the load() function while creating the adapter object in your activity or fragment.
In the overided load function implement your loadmore call
For a detail understanding I wrote a blog post and example project get it here
http://sab99r.com/blog/recyclerview-endless-load-more/
MyAdapter.java
public abstract class MyAdapter extends RecyclerView.Adapter<ViewHolder>{
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//check for last item
if ((position >= getItemCount() - 1))
load();
}
public abstract void load();
}
MyActivity.java
public class MainActivity extends AppCompatActivity {
List<Items> items;
MyAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
adapter=new MyAdapter(items){
#Override
public void load() {
//implement your load more here
Item lastItem=items.get(items.size()-1);
loadMore();
}
};
}
}
My answer is a modified version of Noor. I passed from a ListView where i had EndlessScrollListener (that you can find easily in many answers on SO) to a RecyclerView so i wanted a EndlessRecyclScrollListener to easily update my past listener.
So here is the code, hope it helps:
public abstract class EndlessScrollRecyclListener extends RecyclerView.OnScrollListener
{
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
private int startingPageIndex = 0;
private int currentPage = 0;
#Override
public void onScrolled(RecyclerView mRecyclerView, int dx, int dy)
{
super.onScrolled(mRecyclerView, dx, dy);
LinearLayoutManager mLayoutManager = (LinearLayoutManager) mRecyclerView
.getLayoutManager();
visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
onScroll(firstVisibleItem, visibleItemCount, totalItemCount);
}
public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount)
{
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0)
{
this.loading = true;
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && (totalItemCount > previousTotalItemCount))
{
loading = false;
previousTotalItemCount = totalItemCount;
currentPage++;
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem +
visibleThreshold))
{
onLoadMore(currentPage + 1, totalItemCount);
loading = true;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page, int totalItemsCount);
}
For me, it's very simple:
private boolean mLoading = false;
mList.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int totalItem = mLinearLayoutManager.getItemCount();
int lastVisibleItem = mLinearLayoutManager.findLastVisibleItemPosition();
if (!mLoading && lastVisibleItem == totalItem - 1) {
mLoading = true;
// Scrolled to bottom. Do something here.
mLoading = false;
}
}
});
Be careful with asynchronous jobs: mLoading must be changed at the end of the asynchronous jobs. Hope it will be helpful!
With the power of Kotlin's extension functions, the code can look a lot more elegant. Put this anywhere you want (I have it inside an ExtensionFunctions.kt file):
/**
* WARNING: This assumes the layout manager is a LinearLayoutManager
*/
fun RecyclerView.addOnScrolledToEnd(onScrolledToEnd: () -> Unit){
this.addOnScrollListener(object: RecyclerView.OnScrollListener(){
private val VISIBLE_THRESHOLD = 5
private var loading = true
private var previousTotal = 0
override fun onScrollStateChanged(recyclerView: RecyclerView,
newState: Int) {
with(layoutManager as LinearLayoutManager){
val visibleItemCount = childCount
val totalItemCount = itemCount
val firstVisibleItem = findFirstVisibleItemPosition()
if (loading && totalItemCount > previousTotal){
loading = false
previousTotal = totalItemCount
}
if(!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)){
onScrolledToEnd()
loading = true
}
}
}
})
}
And then use it like this:
youRecyclerView.addOnScrolledToEnd {
//What you want to do once the end is reached
}
This solution is based on Kushal Sharma's answer. However, this is a bit better because:
It uses onScrollStateChanged instead of onScroll. This is better because onScroll is called every time there is any sort of movement in the RecyclerView, whereas onScrollStateChanged is only called when the state of the RecyclerView is changed. Using onScrollStateChanged will save you CPU time and, as a consequence, battery.
Since this uses Extension Functions, this can be used in any RecyclerView you have. The client code is just 1 line.
Most answer are assuming the RecyclerView uses a LinearLayoutManager, or GridLayoutManager, or even StaggeredGridLayoutManager, or assuming that the scrolling is vertical or horyzontal, but no one has posted a completly generic answer.
Using the ViewHolder's adapter is clearly not a good solution. An adapter might have more than 1 RecyclerView using it. It "adapts" their contents. It should be the RecyclerView (which is the one class which is responsible of what is currently displayed to the user, and not the adapter which is responsible only to provide content to the RecyclerView) which must notify your system that more items are needed (to load).
Here is my solution, using nothing else than the abstracted classes of the RecyclerView (RecycerView.LayoutManager and RecycerView.Adapter):
/**
* Listener to callback when the last item of the adpater is visible to the user.
* It should then be the time to load more items.
**/
public abstract class LastItemListener extends RecyclerView.OnScrollListener {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// init
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (layoutManager.getChildCount() > 0) {
// Calculations..
int indexOfLastItemViewVisible = layoutManager.getChildCount() -1;
View lastItemViewVisible = layoutManager.getChildAt(indexOfLastItemViewVisible);
int adapterPosition = layoutManager.getPosition(lastItemViewVisible);
boolean isLastItemVisible = (adapterPosition == adapter.getItemCount() -1);
// check
if (isLastItemVisible)
onLastItemVisible(); // callback
}
}
/**
* Here you should load more items because user is seeing the last item of the list.
* Advice: you should add a bollean value to the class
* so that the method {#link #onLastItemVisible()} will be triggered only once
* and not every time the user touch the screen ;)
**/
public abstract void onLastItemVisible();
}
// --- Exemple of use ---
myRecyclerView.setOnScrollListener(new LastItemListener() {
public void onLastItemVisible() {
// start to load more items here.
}
}
Although the accepted answer works perfectly, the solution below uses addOnScrollListener since setOnScrollListener is deprecated, and reduces number of variables, and if conditions.
final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
feedsRecyclerView.setLayoutManager(layoutManager);
feedsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
if ((layoutManager.getChildCount() + layoutManager.findFirstVisibleItemPosition()) >= layoutManager.getItemCount()) {
Log.d("TAG", "End of list");
//loadMore();
}
}
}
});
This is how I do it, simple and short:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
if(!recyclerView.canScrollVertically(1) && dy != 0)
{
// Load more results here
}
}
});
Although there are so many answers to the question, I would like to share our experience of creating the endless list view. We have recently implemented custom Carousel LayoutManager that can work in the cycle by scrolling the list infinitely as well as up to a certain point. Here is a detailed description on GitHub.
I suggest you take a look at this article with short but valuable recommendations on creating custom LayoutManagers: http://cases.azoft.com/create-custom-layoutmanager-android/
OK, I did it by using the onBindViewHolder method of RecyclerView.Adapter.
Adapter:
public interface OnViewHolderListener {
void onRequestedLastItem();
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
...
if (position == getItemCount() - 1) onViewHolderListener.onRequestedLastItem();
}
Fragment (or Activity):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
contentView = inflater.inflate(R.layout.comments_list, container, false);
recyclerView = (RecyclerView) mContentView.findViewById(R.id.my_recycler_view);
adapter = new Adapter();
recyclerView.setAdapter(adapter);
...
adapter.setOnViewHolderListener(new Adapter.OnViewHolderListener() {
#Override
public void onRequestedLastItem() {
//TODO fetch new data from webservice
}
});
return contentView;
}
recyclerList.setOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx,int dy)
{
super.onScrolled(recyclerView, dx, dy);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView,int newState)
{
int totalItemCount = layoutManager.getItemCount();
int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
if (totalItemCount> 1)
{
if (lastVisibleItem >= totalItemCount - 1)
{
// End has been reached
// do something
}
}
}
});
I would try to extend used LayoutManager (e.g. LinearLayoutManager) and override scrollVerticallyBy() method. Firstly, I would call super first and then check returned integer value. If the value equals to 0 then a bottom or a top border is reached. Then I would use findLastVisibleItemPosition() method to find out which border is reached and load more data if needed. Just an idea.
In addition, you can even return your value from that method allowing overscroll and showing "loading" indicator.
I achieved an infinite scrolling type implementation using this logic in the onBindViewHolder method of my RecyclerView.Adapter class.
if (position == mItems.size() - 1 && mCurrentPage <= mTotalPageCount) {
if (mCurrentPage == mTotalPageCount) {
mLoadImagesListener.noMorePages();
} else {
int newPage = mCurrentPage + 1;
mLoadImagesListener.loadPage(newPage);
}
}
With this code when the RecyclerView gets to the last item, it increments the current page and callbacks on an interface which is responsible for loading more data from the api and adding the new results to the adapter.
I can post more complete example if this isn't clear?
For people who use StaggeredGridLayoutManager here is my implementation, it works for me.
private class ScrollListener extends RecyclerView.OnScrollListener {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
firstVivisibleItems = mLayoutManager.findFirstVisibleItemPositions(firstVivisibleItems);
if(!recyclerView.canScrollVertically(1) && firstVivisibleItems[0]!=0) {
loadMoreImages();
}
}
private boolean loadMoreImages(){
Log.d("myTag", "LAST-------HERE------");
return true;
}
}
There is an easy to use library for this named paginate . Supports both ListView and RecyclerView ( LinearLayout , GridLayout and StaggeredGridLayout).
Here is the link to the project on Github
My way to detect loading event is not to detect scrolling, but to listen whether the last view was attached. If the last view was attached, I regard it as timing to load more content.
class MyListener implements RecyclerView.OnChildAttachStateChangeListener {
RecyclerView mRecyclerView;
MyListener(RecyclerView view) {
mRecyclerView = view;
}
#Override
public void onChildViewAttachedToWindow(View view) {
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
RecyclerView.LayoutManager mgr = mRecyclerView.getLayoutManager();
int adapterPosition = mgr.getPosition(view);
if (adapterPosition == adapter.getItemCount() - 1) {
// last view was attached
loadMoreContent();
}
#Override
public void onChildViewDetachedFromWindow(View view) {}
}
Create an abstract class and extends RecyclerView.OnScrollListener
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold;
private int firstVisibleItem, visibleItemCount, totalItemCount;
private RecyclerView.LayoutManager layoutManager;
public EndlessRecyclerOnScrollListener(RecyclerView.LayoutManager layoutManager, int visibleThreshold) {
this.layoutManager = layoutManager; this.visibleThreshold = visibleThreshold;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
firstVisibleItem = ((LinearLayoutManager)layoutManager).findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
onLoadMore();
loading = true;
}
}
public abstract void onLoadMore();}
in activity (or fragment) add addOnScrollListener to recyclerView
LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager, 3) {
#Override
public void onLoadMore() {
//TODO
...
}
});
I have a pretty detailed example of how to paginate with a RecyclerView. At a high level, I have a set PAGE_SIZE , lets say 30. So I request 30 items and if I get 30 back then I request the next page. If I get less than 30 items I flag a variable to indicate that the last page has been reached and then I stop requesting for more pages. Check it out and let me know what you think.
https://medium.com/#etiennelawlor/pagination-with-recyclerview-1cb7e66a502b
Here my solution using AsyncListUtil, in the web says:
Note that this class uses a single thread to load the data, so it suitable to load data from secondary storage such as disk, but not from network.
but i am using odata to read the data and work fine.
I miss in my example data entities and network methods.
I include only the example adapter.
public class AsyncPlatoAdapter extends RecyclerView.Adapter {
private final AsyncPlatoListUtil mAsyncListUtil;
private final MainActivity mActivity;
private final RecyclerView mRecyclerView;
private final String mFilter;
private final String mOrderby;
private final String mExpand;
public AsyncPlatoAdapter(String filter, String orderby, String expand, RecyclerView recyclerView, MainActivity activity) {
mFilter = filter;
mOrderby = orderby;
mExpand = expand;
mRecyclerView = recyclerView;
mActivity = activity;
mAsyncListUtil = new AsyncPlatoListUtil();
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.plato_cardview, parent, false);
// Create a ViewHolder to find and hold these view references, and
// register OnClick with the view holder:
return new PlatoViewHolderAsync(itemView, this);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Plato item = mAsyncListUtil.getItem(position);
PlatoViewHolderAsync vh = (PlatoViewHolderAsync) holder;
if (item != null) {
Integer imagen_id = item.Imagen_Id.get();
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
vh.getImage().setVisibility(View.VISIBLE);
vh.getProgress().setVisibility(View.GONE);
String cacheName = null;
String urlString = null;
if (imagen_id != null) {
cacheName = String.format("imagenes/imagen/%d", imagen_id);
urlString = String.format("%s/menusapi/%s", MainActivity.ROOTPATH, cacheName);
}
ImageHelper.downloadBitmap(mActivity, vh.getImage(), vh.getProgress(), urlString, cacheName, position);
} else {
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
//show progress while loading.
vh.getImage().setVisibility(View.GONE);
vh.getProgress().setVisibility(View.VISIBLE);
}
}
#Override
public int getItemCount() {
return mAsyncListUtil.getItemCount();
}
public class AsyncPlatoListUtil extends AsyncListUtil<Plato> {
/**
* Creates an AsyncListUtil.
*/
public AsyncPlatoListUtil() {
super(Plato.class, //my data class
10, //page size
new DataCallback<Plato>() {
#Override
public int refreshData() {
//get count calling ../$count ... odata endpoint
return countPlatos(mFilter, mOrderby, mExpand, mActivity);
}
#Override
public void fillData(Plato[] data, int startPosition, int itemCount) {
//get items from odata endpoint using $skip and $top
Platos p = loadPlatos(mFilter, mOrderby, mExpand, startPosition, itemCount, mActivity);
for (int i = 0; i < Math.min(itemCount, p.value.size()); i++) {
data[i] = p.value.get(i);
}
}
}, new ViewCallback() {
#Override
public void getItemRangeInto(int[] outRange) {
//i use LinearLayoutManager in the RecyclerView
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
outRange[0] = layoutManager.findFirstVisibleItemPosition();
outRange[1] = layoutManager.findLastVisibleItemPosition();
}
#Override
public void onDataRefresh() {
mRecyclerView.getAdapter().notifyDataSetChanged();
}
#Override
public void onItemLoaded(int position) {
mRecyclerView.getAdapter().notifyItemChanged(position);
}
});
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
onRangeChanged();
}
});
}
}
}
if (layoutManager.findLastCompletelyVisibleItemPosition() ==
recyclerAdapter.getItemCount() - 1) {
//load more items.
}
Fair and simple.
This will work.
As #John T suggest. Just use code block below, really short, beauty and simple :D
public void loadMoreOnRecyclerView() {
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull #NotNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1) && dy != 0) {
//Load more items here
}
}
});
}
You can follow my Repo to understand the way that it work.
https://github.com/Nghien-Nghien/PokeAPI-Java/blob/0d8d69d348e068911b883f0ae7791d904cc75cb5/app/src/main/java/com/example/pokemonapi/MainActivity.java
Description info about app like this: https://github.com/skydoves/Pokedex#readme
There is a method public void setOnScrollListener (RecyclerView.OnScrollListener listener) in https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29. Use that
EDIT:
Override onScrollStateChanged method inside the onScrollListener and do this
boolean loadMore = firstVisibleItem + visibleItemCount >= totalItemCount;
//loading is used to see if its already loading, you have to manually manipulate this boolean variable
if (loadMore && !loading) {
//end of list reached
}
Check this every thing is explained in detail:
Pagination using RecyclerView From A to Z
mRecyclerView.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 = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
if (!mIsLoading && !mIsLastPage) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
})
loadMoreItems():
private void loadMoreItems() {
mAdapter.removeLoading();
//load data here from the server
// in case of success
mAdapter.addData(data);
// if there might be more data
mAdapter.addLoading();
}
in MyAdapter :
private boolean mIsLoadingFooterAdded = false;
public void addLoading() {
if (!mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = true;
mLineItemList.add(new LineItem());
notifyItemInserted(mLineItemList.size() - 1);
}
}
public void removeLoading() {
if (mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = false;
int position = mLineItemList.size() - 1;
LineItem item = mLineItemList.get(position);
if (item != null) {
mLineItemList.remove(position);
notifyItemRemoved(position);
}
}
}
public void addData(List<YourDataClass> data) {
for (int i = 0; i < data.size(); i++) {
YourDataClass yourDataObject = data.get(i);
mLineItemList.add(new LineItem(yourDataObject));
notifyItemInserted(mLineItemList.size() - 1);
}
}
None of these answers take into account if the list is too small or not.
Here's a piece of code I've been using that works on RecycleViews in both directions.
#Override
public boolean onTouchEvent(MotionEvent motionEvent) {
if (recyclerViewListener == null) {
return super.onTouchEvent(motionEvent);
}
/**
* If the list is too small to scroll.
*/
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}
return super.onTouchEvent(motionEvent);
}
public void setListener(RecyclerViewListener recycleViewListener) {
this.recyclerViewListener = recycleViewListener;
addOnScrollListener(new OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (recyclerViewListener == null) {
return;
}
recyclerViewListener.scrolling(dy);
if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}
});
}
I let you my aproximation. Works fine for me.
I hope it helps you.
/**
* Created by Daniel Pardo Ligorred on 03/03/2016.
*/
public abstract class BaseScrollListener extends RecyclerView.OnScrollListener {
protected RecyclerView.LayoutManager layoutManager;
public BaseScrollListener(RecyclerView.LayoutManager layoutManager) {
this.layoutManager = layoutManager;
this.init();
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
this.onScroll(recyclerView, this.getFirstVisibleItem(), this.layoutManager.getChildCount(), this.layoutManager.getItemCount(), dx, dy);
}
private int getFirstVisibleItem(){
if(this.layoutManager instanceof LinearLayoutManager){
return ((LinearLayoutManager) this.layoutManager).findFirstVisibleItemPosition();
} else if (this.layoutManager instanceof StaggeredGridLayoutManager){
int[] spanPositions = null; //Should be null -> StaggeredGridLayoutManager.findFirstVisibleItemPositions makes the work.
try{
return ((StaggeredGridLayoutManager) this.layoutManager).findFirstVisibleItemPositions(spanPositions)[0];
}catch (Exception ex){
// Do stuff...
}
}
return 0;
}
public abstract void init();
protected abstract void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy);
}
#kushal #abdulaziz
Why not use this logic instead?
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int totalItemCount, lastVisibleItemPosition;
if (dy > 0) {
totalItemCount = _layoutManager.getItemCount();
lastVisibleItemPosition = _layoutManager.findLastVisibleItemPosition();
if (!_isLastItem) {
if ((totalItemCount - 1) == lastVisibleItemPosition) {
LogUtil.e("end_of_list");
_isLastItem = true;
}
}
}
}
Try below:
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
/**
* Abstract Endless ScrollListener
*
*/
public abstract class EndlessScrollListener extends
RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 10;
// The current offset index of data you have loaded
private int currentPage = 1;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// The total number of items in the data set after the last load
private int previousTotal = 0;
private int firstVisibleItem;
private int visibleItemCount;
private int totalItemCount;
private LayoutManager layoutManager;
public EndlessScrollListener(LayoutManager layoutManager) {
validateLayoutManager(layoutManager);
this.layoutManager = layoutManager;
}
public EndlessScrollListener(int visibleThreshold,
LayoutManager layoutManager, int startPage) {
validateLayoutManager(layoutManager);
this.visibleThreshold = visibleThreshold;
this.layoutManager = layoutManager;
this.currentPage = startPage;
}
private void validateLayoutManager(LayoutManager layoutManager)
throws IllegalArgumentException {
if (null == layoutManager
|| !(layoutManager instanceof GridLayoutManager)
|| !(layoutManager instanceof LinearLayoutManager)) {
throw new IllegalArgumentException(
"LayoutManager must be of type GridLayoutManager or LinearLayoutManager.");
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
if (layoutManager instanceof GridLayoutManager) {
firstVisibleItem = ((GridLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
} else if (layoutManager instanceof LinearLayoutManager) {
firstVisibleItem = ((LinearLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
}
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading
&& (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
// End has been reached do something
currentPage++;
onLoadMore(currentPage);
loading = true;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page);
}
I have created LoadMoreRecyclerView using Abdulaziz Noor Answer
LoadMoreRecyclerView
public class LoadMoreRecyclerView extends RecyclerView {
private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
//WrapperLinearLayout is for handling crash in RecyclerView
private WrapperLinearLayout mLayoutManager;
private Context mContext;
private OnLoadMoreListener onLoadMoreListener;
public LoadMoreRecyclerView(Context context) {
super(context);
mContext = context;
init();
}
public LoadMoreRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public LoadMoreRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}
private void init(){
mLayoutManager = new WrapperLinearLayout(mContext,LinearLayoutManager.VERTICAL,false);
this.setLayoutManager(mLayoutManager);
this.setItemAnimator(new DefaultItemAnimator());
this.setHasFixedSize(true);
}
#Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
if(dy > 0) //check for scroll down
{
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading)
{
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount)
{
loading = false;
Log.v("...", "Call Load More !");
if(onLoadMoreListener != null){
onLoadMoreListener.onLoadMore();
}
//Do pagination.. i.e. fetch new data
}
}
}
}
#Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
}
public void onLoadMoreCompleted(){
loading = true;
}
public void setMoreLoading(boolean moreLoading){
loading = moreLoading;
}
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
}
WrapperLinearLayout
public class WrapperLinearLayout extends LinearLayoutManager
{
public WrapperLinearLayout(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("probe", "meet a IOOBE in RecyclerView");
}
}
}
//Add it in xml like
<your.package.LoadMoreRecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</your.package.LoadMoreRecyclerView>
OnCreate or onViewCreated
mLoadMoreRecyclerView = (LoadMoreRecyclerView) view.findViewById(R.id.recycler_view);
mLoadMoreRecyclerView.setOnLoadMoreListener(new OnLoadMoreListener() {
#Override
public void onLoadMore() {
callYourService(StartIndex);
}
});
callYourService
private void callYourService(){
//callyour Service and get response in any List
List<AnyModelClass> newDataFromServer = getDataFromServerService();
//Enable Load More
mLoadMoreRecyclerView.onLoadMoreCompleted();
if(newDataFromServer != null && newDataFromServer.size() > 0){
StartIndex += newDataFromServer.size();
if (newDataFromServer.size() < Integer.valueOf(MAX_ROWS)) {
//StopLoading..
mLoadMoreRecyclerView.setMoreLoading(false);
}
}
else{
mLoadMoreRecyclerView.setMoreLoading(false);
mAdapter.notifyDataSetChanged();
}
}

Categories

Resources