I have RecyclerView inside NestedScrollView
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.NestedScrollView>
I have this problem in big project and in order to find solution for this problem I have created new project without other views.
This is full code of MainActivity
class MainActivity : AppCompatActivity() {
var mItems = mutableListOf<String>()
var mAdapter = MyAdapter(mItems)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recycler.layoutManager = LinearLayoutManager(this)
recycler.adapter = mAdapter
delayedLoadDataIfPossible(100)
recycler.viewTreeObserver.addOnScrollChangedListener {
delayedLoadDataIfPossible(100)
}
}
private fun delayedLoadDataIfPossible(delay: Long) {
Observable.timer(delay, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
var scrollingReachedEnd = isScrollingReachedEnd()
if (scrollingReachedEnd) {
loadData()
}
}
}
private fun isScrollingReachedEnd(): Boolean {
val layoutManager = LinearLayoutManager::class.java.cast(recycler.layoutManager)
val totalItemCount = layoutManager.itemCount
val lastVisible = layoutManager.findLastVisibleItemPosition()
return lastVisible + 5 >= totalItemCount
}
private fun loadData() {
Observable.timer(5, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { progress.visibility = View.VISIBLE }
.doFinally { progress.visibility = View.GONE }
.subscribe {
for (i in 1..10) {
mItems.add(i.toString())
}
mAdapter.notifyDataSetChanged()
delayedLoadDataIfPossible(100)
}
}
}
I am using isScrollingReachedEnd method to identify is scrolling reaching end of list. If there are less than 5 visible items in the end, I am trying to load new data.
loadData simulates loading data. It adds 10 items to list and notifies adapter about change.
delayedLoadDataIfPossible method should work after some delay because findLastVisibleItemPosition is returning value before items are added to list. In result it is returning wrong value. For example -1 after adding first 10 items.
My problem: when RecyclerView inside NestedScrollView findLastVisibleItemPosition returning wrong value and data loading can not be stopped even there are enough items. There is no such problem when RecyclerView not inside NestedScrollView.
My question: how to get last visible item position from RecyclerView when it is inside NestedScrollView?
the problem is when recyclerView parent is a scrollable ViewGroup like nestedScroll , all items in recyclerView is laid out even its not shown , so the findLastVisibleItemPosition() will always return last item in array even if it not visible , so you have to use scroll listener of parent scrollView
package com.example.myApp;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.widget.NestedScrollView;
import androidx.recyclerview.widget.LinearLayoutManager;
public class MyNestedScroll extends NestedScrollView {
int SCREEN_HEIGHT ;
private IsBottomOfList isBottomOfList ;
private LinearLayoutManager linearLayoutManager ;
private String TAG = "MyNestedScroll";
public MyNestedScroll(@NonNull Context context) {
super(context);
init();
}
public MyNestedScroll(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public MyNestedScroll(@NonNull Context context, @Nullable AttributeSet attrs, int
defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
SCREEN_HEIGHT = getContext().getResources().getDisplayMetrics().heightPixels;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (isBottomOfList != null){
isBottomOfList.isBottomOfList(isVisible());
}
}
public boolean isVisible() {
View view = null;
int childCount ;
if (linearLayoutManager != null) {
childCount = linearLayoutManager.getChildCount();
view = linearLayoutManager.getChildAt(childCount-1);
}else {
Log.v(TAG , "linearLayoutManager == null");
}
if (view == null) {
Log.v(TAG , "view == null");
return false;
}
if (!view.isShown()) {
Log.v(TAG , "!view.isShown()");
return false;
}
Rect actualPosition = new Rect();
view.getGlobalVisibleRect(actualPosition);
int height1 = view.getHeight();
int height2 = actualPosition.bottom- actualPosition.top;
Log.v(TAG , "actualPosition.bottom = "+actualPosition.bottom+"/ HomePage.SCREEN_HEIGHT ="+
HomePage.SCREEN_HEIGHT+" / height1 = "+height1+"/ height2 = "+height2);
return actualPosition.bottom<SCREEN_HEIGHT&&height1==height2;
}
public void setIsBottomOfList(IsBottomOfList isBottomOfList) {
this.isBottomOfList = isBottomOfList;
}
public void setLinearLayoutManager(LinearLayoutManager linearLayoutManager) {
this.linearLayoutManager = linearLayoutManager;
Log.v(TAG , linearLayoutManager == null?"LM == NULL":"LM != NULL");
}
public interface IsBottomOfList {
void isBottomOfList(boolean isBottom);
}
}
and in your activity use only this lines
// call this function after set layoutmanager to your recyclerView
private void initScrollListener() {
nestedScroll.setLinearLayoutManager((LinearLayoutManager)bookingRecyclerView.getLayoutManager());
nestedScroll.setIsBottomOfList(isBottom -> {
if (isBottom){
// here the last item of recyclerView is reached
// do some stuff to load more data
}
});
}
It works fine for me , if not please let me know
You could try this approach:
This is my code for a ScrollingListener for a NestedScrollingView:
@Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
int lastVisibleItemPosition = 0;
int totalItemCount = layoutManager.getItemCount();
if(v.getChildAt(v.getChildCount() - 1) != null) {
if (scrollY >= (v.getChildAt(v.getChildCount()-1).getMeasuredHeight() - v.getMeasuredHeight())
&& scrollY > oldScrollY) {
if (layoutManager instanceof LinearLayoutManager) {
lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
}
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0) {
this.loading = true;
}
}
if (loading && (totalItemCount > previousTotalItemCount)) {
loading = false;
previousTotalItemCount = totalItemCount;
}
if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
currentPage++;
onLoadMore();
loading = true;
}
}
}
}
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With