Recyclerview indicates that reaches the last item - android

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
}
}
})

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();
}
}

Recycler View on Scroll Stop

Basically I want my recyclerview to automatically scroll to a position where the item is not half shown. Like the one in googleplay.
I have written a code
public void scrollToVisible(){
int firstVisibleItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
View view = recyclerView.getLayoutManager().getChildAt(0);
if (firstVisibleItemPosition > 0 && view != null) {
int offsetTop = view.getTop();
if (firstVisibleItemPosition - 1 >= 0 && adapter.getItemCount() > 0) {
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(firstVisibleItemPosition - 1, offsetTop);
}
}
}
The problem comes next. I dont know where to put this code. I have a vague idea to put it when the recyclerview stops on scrolling but I've been searching for quite some time now and i cant find such a method. when i put it on the onScroll some unexpected behavior comes out
You may create a CustomRecyclerView extending RecyclerView
public class CustomRecyclerView extends RecyclerView {
#Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
// check if scrolling has stopped
if (state == SCROLL_STATE_IDLE) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
// use code here
}
}
If it maybe of any help to someone:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.d("y value",String.valueOf(dy));
if (dy > 0) {
//scrolling up
} else {
// Scrolling down
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
// Do something
Log.e("SCROLL_STATE_FLING","SCROLL_STATE_FLING");
} else if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
Log.e("SCROLLTOUCH_SCROLL","SCROLL_STATE_TOUCH_SCROLL");
//slideUp(party_view);
// Do something
} else if (newState==AbsListView.OnScrollListener.SCROLL_STATE_IDLE){
// Do something
//slideDown(party_view);
Log.e("SCROLL_STATE_IDLE","SCROLL_STATE_IDLE");
}
}
});
complete example with UI synchronization
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Handler handler = new Handler(getMainLooper());
if (newState == 0) {
handler.removeCallbacks(MainActivity.this::hideFab);
handler.postDelayed(MainActivity.this::showFab, 400);
} else {
handler.removeCallbacks(MainActivity.this::showFab);
handler.postDelayed(MainActivity.this::hideFab, 100);
}
}
});
private void hideFab() {
addFile.hide();
addText.hide();
camera.hide();
}
private void showFab() {
addFile.show();
addText.show();
camera.show();
}

Recyclerview onscrolllistener not working when setNestedScrollingEnabled to false

I want to implement pagination with recyclerView, for this I add addOnScrollListener to the recyclerView but I am having trouble with RecyclerView.OnScrollListener not working when I set rvGridExplore.setNestedScrollingEnabled(false);
But when I remove rvGridExplore.setNestedScrollingEnabled(false); it is working fine,
I don't know how to handle this.
Here is code:
rvGridExplore = (RecyclerView) view.findViewById(R.id.rvGridExplore);
final GridLayoutManager glm = new GridLayoutManager(context,2);
// rvGridExplore.setNestedScrollingEnabled(false);
rvGridExplore.setLayoutManager(glm);
// final int visibleItemCount,totalCount,pastVisibleItems;
rvGridExplore.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Log.v("scrollll","state changed");
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
int totalCount = glm.getItemCount();
int visibleItemCount = glm.getChildCount();
int pastVisibleItems = glm.findFirstVisibleItemPosition();
if (loading) {
if ((visibleItemCount + pastVisibleItems) >= totalCount) {
Log.v("scroll","scrolled"+pastVisibleItems);
}
}
}
}
});
Do add setOnScrollChangeListner to your NestedScrollView
nestedScrollview.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY == (v.getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) {
if(loading)
onClick();
loading=false;
}
}
});
after loading data from server set boolean loading=true.
This question may be old, but to help others who stumbled upon this problem, i would like to share what i did. I had to implement onScroll Listener to recyclerview to load data from server and to make some UI changes. And also needed swipeRefresh Layout for refreshing data.
This was my xml file structure,
-RelativeLayout
-SwipeRefreshLayout
-NestedScrollView
-LinearLayout(Vertical)
-Multiple views required
After this, to detect up and down scrolling i implemented setOnScrollListener to the NestedScrollView.
Normal usage of SwipeRefreshLayout to refresh data.
And to load more data i implemented the logic inside onScrollListener of NestedScrollingView.
if (scrollY == (v.getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) {
// Load More Data
}
If you are recyclerView is embedded in any of the NestedScrollView, then you are supposed to attach the onScrollListener to NestedScrollView.
This will work!
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY == (v.getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) {
totalItemCount = gridLayoutManager.getItemCount();
lastVisibleItem = gridLayoutManager.findLastVisibleItemPosition();
if (!loading
&& totalItemCount <= (lastVisibleItem + visibleThreshold)) {
// End has been reached
// Do something
if (onLoadMoreListener != null) {
onLoadMoreListener.onLoadMore();
}
loading = true;
}
}
}
});
}
You said in a comment to your question "
it is under NestedScrollView which is under coordinator layout, if i remove this, Toolbar is not scrolling up". This is a mistake.
I have found that you cannot have it both ways, the CoordinatorLayout behaviour breaks when you have a RecyclerView inside a NestedScrollView to which you've added the behaviour. You need to use one or the other.
When you have a RecyclerView inside a NestedScrollView it will work as long as you set RecyclerView.setNestedScrollingEnabled(false), but as you found out this means that the OnScrollListener is not called.
The only way for all components to work correctly is to remove the NestedScrollView, make sure you do not set nesting scroll to false and work from there. Otherwise the RecyclerView.OnScrollListener events will not fire correctly.
Step 1 : Create EndlessRecyclerOnScrollListener
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
public static String TAG = EndlessRecyclerOnScrollListener.class.getSimpleName();
// use your LayoutManager instead
private LinearLayoutManager llm;
public EndlessRecyclerOnScrollListener(LinearLayoutManager sglm) {
this.llm = llm;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1)) {
onScrolledToEnd();
}
}
public abstract void onScrolledToEnd();
}
Step 2: Apply scroll listener to recycler view.
recyclerview.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager) {
#Override
public void onScrolledToEnd() {
Log.e("Position", "Last item reached");
if (loadMore == true) {
// put your Load more code
// add 10 by 10 to tempList then notify changing in data
}
}
});
remove the nested scroll view use linear or relative layout instead of it as root element then you can write recyclerview.setNestedScrollEnabled(false);

Detect when RecyclerView reaches the bottom most position while scrolling

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
}
}

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
}
})

Categories

Resources