9

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?

Joe Rakhimov
  • 3,734
  • 7
  • 36
  • 91
  • have u set `recyclerView.setNestedScrollingEnabled(false);` – AskNilesh Sep 07 '18 at 08:11
  • have u used findOneVisibleChild(layoutManager.getChildCount() - 1, -1, false, true) – Ashvin solanki Sep 07 '18 at 08:14
  • 1
    @NileshRathod I have tried to set nestedScrollingEnabled false but it did not help – Joe Rakhimov Sep 07 '18 at 09:30
  • Just as a guess - would it make any difference if you put your RecyclerView into a fixed-size parent layout? – algrid Sep 11 '18 at 09:42
  • @algrid I tried to put RecyclerView into fixed-size parent, it is working perfectly. But according to project requirements it should be below of other views and all of them should be scrollable. For this reason I put RecyclerView into NestedScrollView – Joe Rakhimov Sep 11 '18 at 09:47
  • @JoeRakhimov I mean a fixed-size layout inside your NestedScrollView. – algrid Sep 11 '18 at 09:50
  • @algrid thank you for your suggestion but it did not help – Joe Rakhimov Sep 11 '18 at 10:43
  • 2
    Is [this](https://stackoverflow.com/a/50688421/6287910) the issue? – Cheticamp Sep 11 '18 at 19:12
  • 2
    I would actually put those "above" views right into the `RecyclerView` itself. It means that the adapter has to manage that data, which can interfere with separation of concerns; however it gets the job done. Now you didn't post your entire activity layout with all the included layouts so I don't know if the approach is appropriate. Post more layouts and code, then I might be able to answer this for you. List models do _not_ have to be homogeneous. Consider that the `RecyclerView.Adapter` has the `getItemViewType()` method so you can inflate different views based on the item position. – kris larson Sep 13 '18 at 13:11

3 Answers3

3

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

Sherif farid
  • 176
  • 1
  • 4
2

You could try this approach:

  1. Find RecyclerView inside NestedScrollingView with getChildAt() method.
  2. Get LayoutManager from RecyclerView.
  3. Find lastVisiblePosition().

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!

Juanje
  • 800
  • 1
  • 6
  • 24
-1

If your RecyclerView is inside a NestedScrollView, you need to add recyclerView.setNestedScrollingEnabled(false) to it.

Also another note (which is unrelated to your question is that you should definitely keep a reference to those RxJava subscriptions and dispose them on Fragment/Adtivity's onStop, since they can cause memory leak issues.

Adib Faramarzi
  • 2,723
  • 3
  • 23
  • 36
  • setNestedScrollingEnabled doesn't solve the problem. findLastVisibleItemPosition would still return last position every time – ildar ishalin May 14 '20 at 07:17