26

I have two RecyclerViews placed vertically in a LinearLayout. I need to make both of them scrollable and that is why I have put the LinearLayout inside NestedScrollView

This is the my layout file.

<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbars="none">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/featured_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/all_topic_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

Also, I am disabling nested scrolling in Java code.

disableNestedScrolling(findViewById(R.id.all_topic_list));
disableNestedScrolling(findViewById(R.id.featured_list));

My RecylerView library version is 26.1.0

This works fine perfectly, but then onBindViewHolder method is getting called for all the items in the list. Ideally it should only be called for the visible items in the list.

I think the issue is happening because I am giving wrap_content to the RecyclerView. A lot of answers on this question suggest that the issue is solved in v23.2.1, but I am already using v26.1.0. How to solve this issue?

thedarkpassenger
  • 7,158
  • 3
  • 37
  • 61
  • 1
    I think you might check the [answer here](https://stackoverflow.com/a/39060431/3145960). Instead of having two separate `RecyclerView`, you might consider having two sets of data in a single `RecyclerView` for better optimization of your views. – Reaz Murshed Jun 02 '18 at 10:00
  • The data is coming from two different apis and thus we need to maintain two different recyclerview. Also, the first one is horizontal recyclerview and the second one is vertical, so not possible for my case. – thedarkpassenger Jun 02 '18 at 10:06
  • I second the suggestion about using a single `RecyclerView`. You could have the first element to be another horizontal `RecyclerView` . I am not sure why having data coming from two apis is an issue, it's just matter of creating the adapter to properly handle different view types. – rciovati Jun 04 '18 at 19:34

4 Answers4

23

I had exactly the same problem. RecyclerViews are not meant to be placed inside scroll containers with the same scroll direction. The view recycling only works when the height is set to MATCH_PARENT.

Depending on the complexity of the content inside of the NestedScrollView and the anticipated amount of RecyclerView items:

  1. Ignore the problem. If there are only a few simple items, you may not need view recycling at all.

    When I hit the problem, I analysed the layouts of other popular apps: For example, WhatsApp only uses RecyclerViews (or ListViews with view recycling) in some parts of their app.

    Particularly, this group settings screen with hundreds of possible items is made of multiple ListViews wrapped by a ScrollView, without any view recycling.

  2. Replace the NestedScrollView with a single ReyclerView with multiple item types and put all of your scrollable content inside of it. This is the way to go if you need view recycling.

    Beware that you also have to convert all the other content in the NestedScrollView (headers and footers, spacing) to RecyclerView items with their own ViewHolders.

    If the setup is rather simple, I would recommend you to implement it without additional libraries, following the link above.


There are a few different libraries available to solve your problem (all of them follow the second approach with a single RecyclerView), but most come with a lot of extra features which you may not need:

RendererRecyclerViewAdapter

It comes with a ViewRenderer/ViewModel interface, which works like a "partial" RecyclerView for a single item type. You would create one for every item type and then register them in a single adapter.

Epoxy

A library/framework create by airbnb and used heavily in their app. They have a lot of scrollable content (similar to a web page) with a lot of different item types. Epoxy also helps with the composition of the different items on a page, and it handles animations when the content or its order changes. Too much if you only need it for a single screen.

Litho

A complete UI framework created by Facebook which comes with it's own rendering engine, a replacement for xml layouts and much more. As far as I understand, it allows you to do to handle large amounts of items (like the Facebook timeline) and the view recycling is handled automatically. Like Epoxy, you would only use this if your app includes things like endless scrolling with a lot of different item types and you really need the performance.


I tried Epoxy and RendererRecyclerViewAdapter, but after all I created my own multiple item type adapter. It can be created in less than 100 lines of code.

boformer
  • 28,207
  • 10
  • 81
  • 66
1

Starting from RecyclerView:1.2.0-alpha04 we can use ConcatAdapter to solve this problem https://developer.android.com/reference/androidx/recyclerview/widget/ConcatAdapter

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
R00We
  • 1,931
  • 18
  • 21
0

I tried your problem by adding 20 items in each recyclerview, with NestedScrollView application called onBindViewHolder method 40 times. As you disabling nested scrolling in Java code i suggest to use Scrollview. By using ScrollView application called onBindViewHolder 33 times.


If you fix your recyclerView's height to specific size instead of "match-parent" it will reduce call to onBindViewHolder greatly.

<ScrollView 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:fillViewport="false">

<android.support.v7.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.vishal.my2.MainActivity">
<android.support.v7.widget.RecyclerView
    android:id="@+id/featured_list"
    android:layout_width="match_parent"
    android:layout_height="300dp" />

<android.support.v7.widget.RecyclerView
    android:id="@+id/all_topic_list"
    android:layout_width="match_parent"
    android:layout_height="300dp" />
</android.support.v7.widget.LinearLayoutCompat>
</ScrollView>

If Specifying hardcoded value to recyclerView's height does not meet your application requirement then you can try using ListView instead of recyclerView. pardon me if i am wrong, This was my first time answering any question.

-1

Add this to nested scroll view android:fillViewport="false"

jatin rana
  • 825
  • 1
  • 9
  • 21
  • This does not work. Also will you care to explain why this would have worked according to you? – thedarkpassenger Jun 02 '18 at 10:09
  • android:fillViewport="true" will make recyclerview to load all item at once but setting it to false will make recyclerview to wrap its content. Also i can see in your code that you have disabled nested scrolling in that case you will not be able to scroll recyclerviews separately. – jatin rana Jun 02 '18 at 10:15