Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView getChildCount() and getItemCount() returns same value

RecyclerView has an InfiniteScrollListener and inside that listener, in OnScrolled() method I calculate visible and total item count to check if new items should be loaded. When totalItemCount and visibleItemCount are the same, it causes an infinite loop of loading. The listener works flawlessly with my other RecyclerViews which does NOT have CoordinatorLayout or NestedScrollView as a parent. I want to keep this structure because the customer won't accept a change.

I've got a fragment inside an activity that has a layout like this

CoordinatorLayout{
 NestedScrollView{
    RecyclerView{
    }
 }
}

sdk versions

compileSdkVersion 24
buildToolsVersion "23.0.3"
...
minSdkVersion 21
targetSdkVersion 24

fragment_profile.xml as parent layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay"
        app:layout_behavior="com.paxotic.paxira.util.behavior.AppBarFlingBehavior">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="@dimen/app_bar_height_small"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#000000"
                app:layout_collapseMode="parallax">

                <ImageView
                    android:id="@+id/img_background"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:alpha="0.5"
                    android:scaleType="centerCrop"
                    tools:src="@drawable/bg_greeting_activity" />

            </RelativeLayout>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <include
        layout="@layout/activity_profile_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <android.support.design.widget.FloatingActionButton
       ... />

</android.support.design.widget.CoordinatorLayout>

fragment_profile.xml as child layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    ...

    <android.support.v7.widget.RecyclerView
        android:id="@+id/feedRecycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:paddingBottom="@dimen/spacing_normal"
        tools:listitem="@layout/list_item_activity" />

    ...

</android.support.v4.widget.NestedScrollView>

ProfileFragment.java: relative function

private void init() {

    feedAdapter = new FeedAdapter(host);
    feedAdapter.setHasStableIds(true);

    LinearLayoutManager layoutManager = new LinearLayoutManager(host);
    feedRecycler.setLayoutManager(layoutManager);
    feedRecycler.setNestedScrollingEnabled(false);
    feedRecycler.setHasFixedSize(true);
    feedRecycler.setAdapter(feedAdapter);

    infiniteScrollListener = new InfiniteScrollListener(host, layoutManager) {
        @Override
        public void onLoadMore(int pageIndex) {
            if (!feedAdapter.isLoading) {
                loadFeed(pageIndex);
            }
        }
    };
    feedRecycler.addOnScrollListener(infiniteScrollListener);
}

InfiniteScrollListener.java

public abstract class InfiniteScrollListener extends RecyclerView.OnScrollListener {
    private int previousTotal = 0; // The total number of items in the dataset after the last load
    private boolean loading = false; // True if we are still waiting for the last set of data to load.
    private static final int VISIBLE_THRESHOLD = 5; // The minimum amount of items to have below your current scroll position before loading more.
    int firstVisibleItem, visibleItemCount, totalItemCount;

    private int current_page = 0;
    private int loadingItemCount;

    private LinearLayoutManager mLinearLayoutManager;

    public InfiniteScrollListener(Context context, LinearLayoutManager linearLayoutManager) {
        this.mLinearLayoutManager = linearLayoutManager;
        loadingItemCount = context.getResources().getInteger(R.integer.feed_pull_item_count);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        if (dy < 0) return;        // check for scroll down

        visibleItemCount = mLinearLayoutManager.getChildCount();
        totalItemCount = mLinearLayoutManager.getItemCount();
        firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();

        synchronized (this) {

            if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + VISIBLE_THRESHOLD)) {
                // End has been reached, Do something
                current_page++;
                onLoadMore(current_page);
                loading = true;
            }
        }
    }

    public abstract void onLoadMore(int current_page);

    public void setLoading(boolean loading) {
        this.loading = loading;
    }

    @Override
    public void onScrollStateChanged(RecyclerView view, int scrollState) {
        // Don't take any action on changed
    }
}
like image 452
dgngulcan Avatar asked Jul 13 '16 21:07

dgngulcan


2 Answers

you are getting the same values from getChildCount() getItemCount(), because the recyclerview will returns the total number of items/child.

you must override one of the method, as per your usage, to get the value you want. Do this in your custom recyclerview adapter.

To do this, add this to your recyclerview adapter:

@Overide
public int getChildCount() {
//return your desired value here after obtaining it
}
like image 164
Kumar Shivam Ray Avatar answered Nov 10 '22 10:11

Kumar Shivam Ray


You get the real count of views. If adapter has 15 items and ((LinearLayoutManager) myRecyclerView.getLayoutManager()).getChildCount() returns you 7, there is two items visible in view due to max height or static height value after you update new item its till persists same height.

The count of items in the model (adapter) and the count of views are not the same thing.

RecyclerView doesn't hold views for all 15 items in memory, it only has 7 in your case. When you scroll, it reuses those 7 views for displaying different items. The purpose of this is improving the rendering performance.

If you want to get 15, then you should use mAdapter.getItemCount() instead.

like image 33
Kavinrajan Avatar answered Nov 10 '22 11:11

Kavinrajan