Endless Scroll RecyclerView with dissimilar array

2019-09-08 02:46发布

问题:

I'm currently using the scrollListener to enable endless scroll - you can find my question and Vilen's answer here:

Adding items to Endless Scroll RecyclerView with ProgressBar at bottom

This works wonders when you have the same dataset size each time you activate your adapter when you scroll For example, in Vilen's code, he is increasing the size of the dataset by the same amount, 15, each time he scrolls.

However, in the real world, this is often not the case. You have dissimilar dataset sizes all the time. In my example, I would like to do the following:

I would like to implement a geospatial search so that it returns a list of places closest to me. Now, if I'm sitting in a desert, my REST API call would return the places within 10km of me and this could just be 4 places (the camel hut, the pyramids, the watering hole and the coconut tree).

Populating 4 items in a recyclerview will not make the recyclerview long enough to allow the it to scroll further so that my app will trigger the API call again to find more places closer to me. The next 10km (20km from my current location) could be a town with lots more places to return, but because the recyclerview is not long enough to scroll, the API call is not made.

I have also made a tiny repo to demonstrate that the scroll listener is not triggered when the recyclerview is too short:

https://github.com/Winghin2517/DissimilarDataSetSizeRV.git

How can I get around this problem?

I almost feel like the scrolllistener should be triggered until the recyclerview fills up the screen but how can I determine when the screen will be filled up as there is no callback for recyclerview-fill-up-screen, as far as I know.

I had tried to stop changing the boolean loading to see that would help but without the boolean checking the status of the load, the progressBar will not necessarily be removed leading to this type of effect:

回答1:

Thanks to this thread, I was able to determine whether the recyclerview is long enough to scroll: How to know if a RecyclerView has enough content to scroll?

I then played with the code a little and it seems to work in this way.

I create two new variables:

final static String PROGRESS_BAR = "progressBar"; //this will be the unique object that will be added to the arraylist when you want to display the progress bar
boolean progressBarAdded; //the progressbar must only be added once so this will keep track of it when it is added.

   //adapter code is changed to take into account when the recyclerview can / cannot scroll
    mAdapter.setOnLoadMoreListener(new MyAdapter.OnLoadMoreListener() {
        @Override
        public void onLoadMore() {
            //add progress item
            //we can scroll and progress bar is not yet added
            if (!progressBarAdded && llm.findLastCompletelyVisibleItemPosition() != mAdapter.getItemCount() - 1) {
                progressBarAdded = true;
                myDataset.add(PROGRESS_BAR);
                mAdapter.notifyItemInserted(myDataset.size() - 1);
            }

            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //remove progress item
                    boolean removed = myDataset.remove(PROGRESS_BAR);
                    if (removed) {
                        mAdapter.notifyItemRemoved(myDataset.size());
                        progressBarAdded = false; //progress bar is now removed
                    }
                    //add items one by one
                    for (int i = 0; i < 3; i++) {
                        myDataset.add("Item" + (myDataset.size() + 1));
                        mAdapter.notifyItemInserted(myDataset.size());
                    }
                //we can scroll
                if(llm.findLastCompletelyVisibleItemPosition()!=mAdapter.getItemCount()-1) {
                    mAdapter.setLoaded();
                }
            }
        }, 500);

I then change the scrolllistener like this:

    if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {

        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
                if (totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    // End has been reached
                    // Do something
                    if (onLoadMoreListener != null) {
                        onLoadMoreListener.onLoadMore();
                    }
                    if (linearLayoutManager.findLastCompletelyVisibleItemPosition() != MyAdapter.this.getItemCount() - 1) { //determines whether the recyclerview is full or not. If not full, this will cause it to continue to load
                        loading = true;
                    }
                }
            }
        });
    }

Also make sure you take into account the new getItemType as each time it detects the string "progressBar", it must load the viewholder for the progressBar:

@Override
public int getItemViewType(int position) {
    return mDataset.get(position) != MainActivity.PROGRESS_BAR ? VIEW_ITEM : VIEW_PROG;
}


回答2:

You could try this approach (The code below is from one of my apps, so you will have to fill the gaps, I just added the parts that shall solve your problem)>

public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener 
// Or use yourListView.setOnScrollListener(this);
{

    int preLast = -1;

    @Override
    public void onScroll(AbsListView lw, final int aFirstVisibleItemIndex, final int aVisibleItemCount, final int aTotalItemsCount) {
        int id = mListView.getId(); // Or use case android.R.id.list:     

        if(lw.getId() == id) {
                     // Sample calculation to determine if the last item is fully visible.
            final int lastItem = aFirstVisibleItemIndex + aVisibleItemCount;
            if(lastItem > 0 && lastItem == aTotalItemsCount) // lastItem > 0 helps to avoid continuous call if the list is empty.
            {
                if(preLast != lastItem) { //to avoid multiple calls for last item
                    preLast = lastItem;

                }
              }
        }
      }
}

@Override //it will be called before any calls to Adapter.getView(.....)
public void onScrollStateChanged(AbsListView view, int scrollState) {

}

And now and whenever you decide that there is no more data to fetch from your sever (to populate more items in your list) and you want to avoid the listview to keep calling / activating your (on going loading animation), you can simply set preLast value to lastItem value.