341

I'm trying to implement a layout which contains RecyclerView and ScrollView at the same layout.

Layout template:

<RelativeLayout>
    <ScrollView android:id="@+id/myScrollView">

       <unrelated data>...</unrealated data>

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

Problems: I can scroll until the last element of ScrollView.

Things I tried:

  1. Card view inside the ScrollView (now ScrollView contains RecyclerView) - can see the card up until the RecyclerView.
  2. Initial thought was to implement this ViewGroup using RecyclerView instead of ScrollView where one of it's views type is the CardView, but I got the exact same results as with the ScrollView.
SerjantArbuz
  • 982
  • 1
  • 12
  • 16
royB
  • 12,779
  • 15
  • 58
  • 80
  • checkout this approach: http://stackoverflow.com/a/21878703/684582 – Alécio Carvalho Jan 26 '15 at 09:42
  • 18
    a simple solution in many of these cases is to use `NestedScrollView` instead, as it handles a lot of scrolling issues – Richard Le Mesurier Feb 19 '16 at 14:21
  • 1
    Richard gave you the answer in February. Use a `NestedScrollView` instead of a `ScrollView`. That's exactly what it's for. – Mike M. May 21 '16 at 23:42
  • 3
    Doesn't change a thing for me. – Luke Allison Jun 01 '16 at 06:17
  • 2
    For future reference, if anybody is experiencing similar issue **only** marshmallow/nougat (API 23, 24) devices, check my workaround at http://stackoverflow.com/a/38995399/132121 – Hossain Khan Aug 17 '16 at 11:32
  • RecyclerView doesn't recycle views in this layout hierarchy. You can verify by adding hundreds of items and checking the jank. – kp91 Jun 13 '18 at 10:18
  • Try disabling the touch of recycler view when scrollview is being touched. https://stackoverflow.com/a/48477270/3463571 – Basit Ali Jan 07 '22 at 12:07
  • @RichardLeMesurier Also destroys the whole purpose of the RecyclerView. Views are not recycled so you end up having as many ViewHolder classes as items you have in the adapter. – Farid Feb 25 '22 at 12:54
  • @Farid with respect the OP presents a question with trade offs, and a request for specific nested scrolling behavior, if a `RecyclerView` inside some kind of scrolling layout. Accepted answer accomplishes the OP aims, because Google engineers realised there was a use case for this type of UI design. Obviously we hopefully all agree that if your use case is hitting jank due to non-recycling of views, then the UI design needs to be revisited and updated. However for most cases in the last 6 or 7 years, `NestedScrollView` > `ScrollView` for various reasons. – Richard Le Mesurier Feb 25 '22 at 20:54
  • @RichardLeMesurier I get your point but "respecting" OP's use case will mislead thousand of newbies. Almost everyone has rushed to answer the question here rather than giving a little clue of the possible and dangerous pitfall. There are even oneliners without explanation and they got quite some upvotes that someone may think this is quite valid. Not blaming but giving a little insight is sometimes worth more than answer – Farid Feb 27 '22 at 12:14
  • I believe there are still good use cases for this technique today, as there were 6 years ago. It is great that the weaknesses have also been pointed so that any readers are able to form a balanced opinion of their own. – Richard Le Mesurier Feb 27 '22 at 12:52

26 Answers26

815

use NestedScrollView instead of ScrollView

Please go through NestedScrollView reference document for more information.

and add recyclerView.setNestedScrollingEnabled(false); to your RecyclerView

cascal
  • 2,943
  • 2
  • 17
  • 19
Yang Peiyong
  • 11,536
  • 2
  • 21
  • 15
  • 25
    Works with : android.support.v4.widget.NestedScrollView – Cocorico Sep 26 '16 at 13:12
  • 11
    keep ``android:layout_height="wrap_content"`` for the layout inflated for ViewHolder – Sarath Babu Mar 17 '17 at 10:32
  • 6
    In a complex layout `NestedScrollView` lags for me, unlike the `ScrollView`. Searching for a solution without using `NestedScrollView` – Roman Samoilenko Aug 07 '17 at 21:46
  • It's working but my requirement is different, I have RecyclerView inside NestedScrollView and implementing paging for recyclerview and listening for the last position to fetch next data but it's not wokring, if I remove ScrollView or NestedScrollview then it's working fine. Is there any solution for this? – chetan Nov 06 '17 at 06:18
  • For below Lollipop versions: https://stackoverflow.com/a/43450158/7784230 – Hemant Kaushik Nov 10 '17 at 17:44
  • In my case, I did took Horizontal RecyclerView within HorizontalScrollView and I also write this statement but It can't show me all the data which I stored in horizontal recyclerview – Prince Dholakiya Jan 08 '19 at 05:35
  • 20
    Also you can add android:nestedScrollingEnabled="false" to XML instead of recyclerView.setNestedScrollingEnabled(false); – kostyabakay Feb 11 '19 at 13:31
  • this worked for me but I have expandable items in recyclerview & the last item expands below visible screen . this wasn't the case with scrollView – akshay bhange Mar 27 '19 at 13:15
  • 11
    It worked for me but keep in mind that the items inside recyclerView are not getting recycled. – Mostafa Arian Nejad Apr 24 '19 at 12:57
  • 1
    it only for api level 21, how about level 19? – Nanda Z Jul 26 '19 at 06:46
  • For those who stumble onto this answer and did everything right. hasFixedSize must be set to false on recyclerview for this to work. – Thijs Aug 14 '19 at 16:25
  • @NandaZ, `ViewCompat.setNestedScrollingEnabled(recyclerView, false)`. – CoolMind Nov 26 '19 at 15:08
  • @Thijs, `setHasFixedSize(true)` works right in my case. – CoolMind Nov 26 '19 at 15:10
  • 3
    The correct package to use is now `androidx.core.widget.NestedScrollView` – cascal Jul 26 '21 at 00:26
  • This doesn't work well at all. Suppose you have 1000 items on the RecyclerView, it will create&bind them all right away... – android developer Jul 05 '23 at 20:08
118

I know I am late it the game, but the issue still exists even after google has made fix on the android.support.v7.widget.RecyclerView

The issue I get now is RecyclerView with layout_height=wrap_content not taking height of all the items issue inside ScrollView that only happens on Marshmallow and Nougat+ (API 23, 24, 25) versions.
(UPDATE: Replacing ScrollView with android.support.v4.widget.NestedScrollView works on all versions. I somehow missed testing accepted solution. Added this in my github project as demo.)

After trying different things, I have found workaround that fixes this issue.

Here is my layout structure in a nutshell:

<ScrollView>
  <LinearLayout> (vertical - this is the only child of scrollview)
     <SomeViews>
     <RecyclerView> (layout_height=wrap_content)
     <SomeOtherViews>

The workaround is the wrap the RecyclerView with RelativeLayout. Don't ask me how I found this workaround!!! ¯\_(ツ)_/¯

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:descendantFocusability="blocksDescendants">

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

</RelativeLayout>

Complete example is available on GitHub project - https://github.com/amardeshbd/android-recycler-view-wrap-content

Here is a demo screencast showing the fix in action:

Screencast

Mehdi Dehghani
  • 10,970
  • 6
  • 59
  • 64
Hossain Khan
  • 6,332
  • 7
  • 41
  • 55
  • 7
    Thanks man.. its help a lot.. but scrolling become hard after your solution so i set recyclerview.setNestedScrollingEnabled(false); and now its work like a charm. – Sagar Chavada Feb 16 '17 at 10:15
  • 6
    Is this method recycle views? if we have around hundreds of object to be recycle. This is hack not solution. – androidXP May 25 '17 at 15:11
  • 2
    Yes, @androidXP is right, this hack is not a solution for a long list. My use case was fixed item in a list view which was less than 10. And as for how I found the other workaround, I was trying random things, this was one of them :-) – Hossain Khan Aug 16 '17 at 01:38
  • perfect.solved my problem in adnroid marshmallow and upper. ;) – Amir Ziarati Sep 17 '17 at 08:02
  • I think your solution is better but when I removed "setHasFixedSize(true);" it works more better – mehmet Dec 19 '17 at 11:58
  • 3
    if I use this solution, then onBindView is getting called for all the items in the list, which is not the usecase of recyclerview. – thedarkpassenger Jun 04 '18 at 17:35
  • android:descendantFocusability="blocksDescendants" helped me – Abror Esonaliev Sep 26 '18 at 13:42
  • Please don't ... check your memory usage. This hack is killing what "recycler" means. – Taufik Nur Rahmanda Nov 01 '18 at 02:39
  • As I mentioned earlier, this is intended for a small list. I've also used it for long list with images without issue except memory consumption. So, yes, anybody planning to use this for long list is not a good idea. – Hossain Khan Jan 01 '19 at 12:54
  • This is not working if your data recyclerview source coming from api and you add "load more" feature. it will be load all data and would cause OutOfMemoryError – Nanda Z Mar 04 '19 at 02:40
  • The use case I had was for limited fixed size items that look similar. As I mentioned earlier, don't use if the list size is dynamic and you expect to add more items dynamically. – Hossain Khan May 02 '19 at 23:33
  • 1
    You're genius! Awesome. – Gary Chen Oct 23 '20 at 18:29
  • I used stickyScrollview, and recyclerview was going above the StickyHeader, actually it should go below, after applying your solution, it worked like charm :) great man and thanks a ton. – Selva Jun 25 '21 at 20:27
  • 1
    @HossainKhan thank you. The fix without the nestedscroll worked for me, however its worth pointing out that ONLY RelativeLayout works, I tested Linear and Constraint and it wont work for some reason. – Raykud May 12 '22 at 03:22
  • Only solution which really worked. Thank you! – Daniel Aug 09 '23 at 06:50
57

Although the recommendation that

you should never put a scrollable view inside another scrollable view

Is a sound advice, however if you set a fixed height on the recycler view it should work fine.

If you know the height of the adapter item layout you could just calculate the height of the RecyclerView.

int viewHeight = adapterItemSize * adapterData.size();
recyclerView.getLayoutParams().height = viewHeight;
OWADVL
  • 10,704
  • 7
  • 55
  • 67
Joakim Engstrom
  • 6,243
  • 12
  • 48
  • 67
  • 15
    How to get adapterItemSize in recyclerview any idea? – Krutik May 21 '15 at 06:17
  • I set the height to be fixed but this disabled scrolling for me. How would i re-enable it. I simply did the above code amount of dp to px times size of items. – Ersen Osman Jul 21 '15 at 09:12
  • 1
    It works like a charm! Little correction it should be : int viewHeight = adapterItemSize * adapterData.size(); recyclerView.getLayoutParams().height = viewHeight; – Vaibhav Jani Jul 23 '15 at 11:34
  • 5
    How to find out adapterItemSize? – droidster.me Aug 21 '15 at 07:28
  • 3
    @JoakimEngstrom What is the `adapterItemSize` variable? – IgorGanapolsky Sep 28 '15 at 17:39
  • it will work.but it still consumes some event .not good for performance – Asthme Sep 29 '15 at 11:20
  • when i put recyclerview inside scroll view, my adapter is never call....any suggestion? – H Raval Oct 26 '15 at 11:56
  • For those having problems with touch events, either see if you override onInterceptTouch anywhere, and see what is done there. If you don't maybe it's a good idea too look at that api. Touch events are a tricky thing in android. – Joakim Engstrom Nov 04 '15 at 08:24
  • 2
    Avoid recycler view inside Scroll view, because scroll view give its child infinite space. This causes recycler view having wrap_content as height to measure infinitely in vertical direction(till the last item of recycler view). Instead of using recycler view inside scroll view, use only recycler view with different item types. With this implementation, children of scroll view would behave as as view type. Handle those viewtypes inside recycler view. – abhishesh May 25 '16 at 12:02
  • nested scroll view is made for such a purpose already. this recommendations is not recommended? – Masoud Dadashi Jun 13 '16 at 23:25
  • @MasoudDadashi Yes, you are right, this answer is not the best answer anymore. – Joakim Engstrom Aug 28 '16 at 18:56
  • using simple `wrap_content` as `layout_height` worked for me. No need of calculating the height. – rahulrvp Dec 27 '16 at 10:04
  • 1
    Very bad solution, I mean we use recyclerview just to reuse the old views and if we assign the fixed height to recyclerview by your formula then recyclerview will not reuse that – Umar Ata Mar 22 '18 at 14:17
35

In case setting fixed height for the RecyclerView didn't work for someone (like me), here is what I've added to the fixed height solution:

mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        int action = e.getAction();
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                rv.getParent().requestDisallowInterceptTouchEvent(true);
                break;
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
});
rsicarelli
  • 1,013
  • 1
  • 11
  • 28
Clans
  • 562
  • 8
  • 14
  • 1
    Indeed, this worked well for me. With this in place, I was able to move the scroll view up and down, and when I select the recyclerview for scrolling, it takes priority over the scroll view. Thanks for the tip – PGMacDesign Jun 10 '15 at 21:08
  • 1
    it worked for me too, just be sure to use getParent on a direct child of the scrollview – BigBen3216 Aug 05 '15 at 15:56
  • This method works on cyanogenmod distributions as well. The fixed height solution on cyanogenmod works, but only if the fixed height is the absolute height of all of the items in the list, which defies the point of using the recyclerview in the first place. Upvoted. – HappyKatz Oct 28 '15 at 16:57
  • I also needed recyclerView.setNestedScrollingEnabled(false); – Analizer Dec 06 '16 at 15:25
  • This really worked! It never occurred to me to set a touch listener at the item level. I tried (with no luck) to set a touch listener at the recyclerView level. Great solution. Cheers! – Tamoxin Feb 24 '22 at 10:49
28

The new Android Support Library 23.2 solves that problem, you can now set wrap_content as the height of your RecyclerView and works correctly.

Android Support Library 23.2

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
kevings14
  • 1,986
  • 2
  • 18
  • 19
18

RecyclerViews are fine to put in ScrollViews so long as they aren't scrolling themselves. In this case, it makes sense to make it a fixed height.

The proper solution is to use wrap_content on the RecyclerView height and then implement a custom LinearLayoutManager that can properly handle the wrapping.

Copy this LinearLayoutManager into your project: link

Then wrap the RecyclerView:

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

And set it up like so:

RecyclerView list = (RecyclerView)findViewById(R.id.list);
list.setHasFixedSize(true);
list.setLayoutManager(new com.example.myapp.LinearLayoutManager(list.getContext()));
list.setAdapter(new MyViewAdapter(data));

Edit: This can cause complications with scrolling because the RecyclerView can steal the ScrollView's touch events. My solution was just to ditch the RecyclerView in all and go with a LinearLayout, programmatically inflate subviews, and add them to the layout.

SerjantArbuz
  • 982
  • 1
  • 12
  • 16
jcady
  • 3,850
  • 2
  • 21
  • 21
17

For ScrollView, you could use fillViewport=true and make layout_height="match_parent" as below and put RecyclerView inside:

<ScrollView
    android:fillViewport="true"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_below="@+id/llOptions">

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

No further height adjustment needed through code.

SerjantArbuz
  • 982
  • 1
  • 12
  • 16
mustaq
  • 961
  • 14
  • 16
15

If you put RecyclerView inside NestedScrollView and enable recyclerView.setNestedScrollingEnabled(false);, scrolling will working well.
However, there is a problem

RecyclerView don't recycle

For example, your RecyclerView (inside NestedScrollView or ScrollView) have 100 item.
When Activity launch, 100 item will create (onCreateViewHolder and onBindViewHolder of 100 item will called at same time).
Example, for each item, you will load a large image from API => activity created -> 100 image will load.
It make starting Activity slowness and lagging.
Possible solution:
- Thinking about using RecyclerView with multiple type.

However, if in your case, there are just a few item in RecyclerView and recycle or don't recycle don't affect performance a lot, you can use RecyclerView inside ScrollView for simple

Linh
  • 57,942
  • 23
  • 262
  • 279
  • Show how can I make RecyclerView recycle even inside ScollView? Thanks! – Liar Oct 02 '18 at 07:50
  • 2
    @Liar, currently there is no way to make `RecyclerView` recycle after put it to `ScrollView`. If you want recycle, thinking about another approach (like using RecyclerView with multiple type) – Linh Oct 02 '18 at 07:55
  • you can give the recycler view a set height – martinseal1987 Oct 24 '20 at 11:29
14

Calculating RecyclerView's height manually is not good, better is to use a custom LayoutManager.

The reason for above issue is any view which has it's scroll (ListView, GridView, RecyclerView) failed to calculate it's height when add as a child in another view has scroll. So overriding its onMeasure method will solve the issue.

Please replace the default layout manager with the below:

public class MyLinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

    private static boolean canMakeInsetsDirty = true;
    private static Field insetsDirtyField = null;

    private static final int CHILD_WIDTH = 0;
    private static final int CHILD_HEIGHT = 1;
    private static final int DEFAULT_CHILD_SIZE = 100;

    private final int[] childDimensions = new int[2];
    private final RecyclerView view;

    private int childSize = DEFAULT_CHILD_SIZE;
    private boolean hasChildSize;
    private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
    private final Rect tmpRect = new Rect();

    @SuppressWarnings("UnusedDeclaration")
    public MyLinearLayoutManager(Context context) {
        super(context);
        this.view = null;
    }

    @SuppressWarnings("UnusedDeclaration")
    public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        this.view = null;
    }

    @SuppressWarnings("UnusedDeclaration")
    public MyLinearLayoutManager(RecyclerView view) {
        super(view.getContext());
        this.view = view;
        this.overScrollMode = ViewCompat.getOverScrollMode(view);
    }

    @SuppressWarnings("UnusedDeclaration")
    public MyLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
        super(view.getContext(), orientation, reverseLayout);
        this.view = view;
        this.overScrollMode = ViewCompat.getOverScrollMode(view);
    }

    public void setOverScrollMode(int overScrollMode) {
        if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER) {
            throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
        }

        if (this.view == null) throw new IllegalStateException("view == null");

        this.overScrollMode = overScrollMode;
        ViewCompat.setOverScrollMode(view, overScrollMode);
    }

    public static int makeUnspecifiedSpec() {
        return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    }

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);

        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
        final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;

        final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
        final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;

        final int unspecified = makeUnspecifiedSpec();

        /** 
         * In case of exact calculations for both dimensions let's 
         * use default "onMeasure" implementation. 
         */
        if (exactWidth && exactHeight) {
            super.onMeasure(recycler, state, widthSpec, heightSpec);
            return;
        }

        final boolean vertical = getOrientation() == VERTICAL;

        initChildDimensions(widthSize, heightSize, vertical);

        int width = 0;
        int height = 0;

        /**
         * It's possible to get scrap views in recycler which are bound to old (invalid) 
         * adapter entities. This happens because their invalidation happens after "onMeasure" 
         * method. As a workaround let's clear the recycler now (it should not cause 
         * any performance issues while scrolling as "onMeasure" is never called whiles scrolling).
         */
        recycler.clear();

        final int stateItemCount = state.getItemCount();
        final int adapterItemCount = getItemCount();

        /**
         * Adapter always contains actual data while state might contain old data
         * (f.e. data before the animation is done).  As we want to measure the view 
         * with actual data we must use data from the adapter and not from  the state.
         */
        for (int i = 0; i < adapterItemCount; i++) {
            if (vertical) {
                if (!hasChildSize) {
                    if (i < stateItemCount) {
                        /**
                         * We should not exceed state count, otherwise we'll get IndexOutOfBoundsException. 
                         * For such items we will use previously calculated dimensions.
                         */
                        measureChild(recycler, i, widthSize, unspecified, childDimensions);
                    } else {
                        logMeasureWarning(i);
                    }
                }
                height += childDimensions[CHILD_HEIGHT];
                if (i == 0) {
                    width = childDimensions[CHILD_WIDTH];
                }
                if (hasHeightSize && height >= heightSize) {
                    break;
                }
            } else {
                if (!hasChildSize) {
                    if (i < stateItemCount) {
                        /**
                         * We should not exceed state count, otherwise we'll get IndexOutOfBoundsException. 
                         * For such items we will use previously calculated dimensions.
                         */
                        measureChild(recycler, i, unspecified, heightSize, childDimensions);
                    } else {
                        logMeasureWarning(i);
                    }
                }
                width += childDimensions[CHILD_WIDTH];
                if (i == 0) {
                    height = childDimensions[CHILD_HEIGHT];
                }
                if (hasWidthSize && width >= widthSize) {
                    break;
                }
            }
        }

        if (exactWidth) {
            width = widthSize;
        } else {
            width += getPaddingLeft() + getPaddingRight();
            if (hasWidthSize) {
                width = Math.min(width, widthSize);
            }
        }

        if (exactHeight) {
            height = heightSize;
        } else {
            height += getPaddingTop() + getPaddingBottom();
            if (hasHeightSize) {
                height = Math.min(height, heightSize);
            }
        }

        setMeasuredDimension(width, height);

        if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
            final boolean fit = (vertical && (!hasHeightSize || height < heightSize))
                || (!vertical && (!hasWidthSize || width < widthSize));

            ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
        }
    }

    private void logMeasureWarning(int child) {
        if (BuildConfig.DEBUG) {
            Log.w("MyLinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." +
                "To remove this message either use #setChildSize() method or don't run RecyclerView animations");
        }
    }

    private void initChildDimensions(int width, int height, boolean vertical) {
        if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
            /** Already initialized, skipping. */
            return;
        }
        if (vertical) {
            childDimensions[CHILD_WIDTH] = width;
            childDimensions[CHILD_HEIGHT] = childSize;
        } else {
            childDimensions[CHILD_WIDTH] = childSize;
            childDimensions[CHILD_HEIGHT] = height;
        }
    }

    @Override
    public void setOrientation(int orientation) {
        /** Might be called before the constructor of this class is called. */
        //noinspection ConstantConditions
        if (childDimensions != null) {
            if (getOrientation() != orientation) {
                childDimensions[CHILD_WIDTH] = 0;
                childDimensions[CHILD_HEIGHT] = 0;
            }
        }
        super.setOrientation(orientation);
    }

    public void clearChildSize() {
        hasChildSize = false;
        setChildSize(DEFAULT_CHILD_SIZE);
    }

    public void setChildSize(int childSize) {
        hasChildSize = true;
        if (this.childSize != childSize) {
            this.childSize = childSize;
            requestLayout();
        }
    }

    private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) {
        final View child;
        try {
            child = recycler.getViewForPosition(position);
        } catch (IndexOutOfBoundsException e) {
            if (BuildConfig.DEBUG) {
                Log.w("MyLinearLayoutManager", "MyLinearLayoutManager doesn't work well with animations. Consider switching them off", e);
            }
            return;
        }

        final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();

        final int hPadding = getPaddingLeft() + getPaddingRight();
        final int vPadding = getPaddingTop() + getPaddingBottom();

        final int hMargin = p.leftMargin + p.rightMargin;
        final int vMargin = p.topMargin + p.bottomMargin;

        /** We must make insets dirty in order calculateItemDecorationsForChild to work. */
        makeInsetsDirty(p);
        /** This method should be called before any getXxxDecorationXxx() methods. */
        calculateItemDecorationsForChild(child, tmpRect);

        final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
        final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);

        final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
        final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically());

        child.measure(childWidthSpec, childHeightSpec);

        dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
        dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;

        /** As view is recycled let's not keep old measured values. */
        makeInsetsDirty(p);
        recycler.recycleView(child);
    }

    private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
        if (!canMakeInsetsDirty) return;

        try {
            if (insetsDirtyField == null) {
                insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
                insetsDirtyField.setAccessible(true);
            }
            insetsDirtyField.set(p, true);
        } catch (NoSuchFieldException e) {
            onMakeInsertDirtyFailed();
        } catch (IllegalAccessException e) {
            onMakeInsertDirtyFailed();
        }
    }

    private static void onMakeInsertDirtyFailed() {
        canMakeInsetsDirty = false;
        if (BuildConfig.DEBUG) {
            Log.w("MyLinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
        }
    }
}
SerjantArbuz
  • 982
  • 1
  • 12
  • 16
Arun Antoney
  • 4,292
  • 2
  • 20
  • 26
14

Try this. Very late answer, but surely helps anyone in future.

  1. Change your ScrollView to NestedScrollView:

    <android.support.v4.widget.NestedScrollView>
    
        <android.support.v7.widget.RecyclerView 
            ...
            ... />
    
    </android.support.v4.widget.NestedScrollView>
    
  2. In your UI code, update it for Recyclerview:

    recyclerView.setNestedScrollingEnabled(false); 
    recyclerView.setHasFixedSize(false);
    
SerjantArbuz
  • 982
  • 1
  • 12
  • 16
Ranjithkumar
  • 16,071
  • 12
  • 120
  • 159
  • 11
    using RecyclerView inside NestedScrollView is calling onBindView for every item in the list even if the item is not visible. Any solution for that problem? – thedarkpassenger Jun 04 '18 at 17:32
  • You just need to give PaddingBottom into LinearLayout that is inside nestedScrollView - @thedarkpassenger – oalpayli Feb 01 '21 at 09:47
10

UPDATE: this answer is out dated now as there are widgets like NestedScrollView and RecyclerView that support nested scrolling.

you should never put a scrollable view inside another scrollable view !

i suggest you make your main layout recycler view and put your views as items of recycler view.

take a look at this example it show how to use multiple views inside recycler view adapter. link to example

Community
  • 1
  • 1
EC84B4
  • 7,676
  • 4
  • 23
  • 34
  • i have a page with more than one **Recycler**, is there any other way to persuade this? some thing like **instagram** or **google play** part comment that load more record when you click on _more comment_ – Ashkan Sep 02 '15 at 11:45
  • make it a single recyclerView and put your views as items for that recycler – EC84B4 Sep 02 '15 at 12:04
  • 6
    That is nonsense. You can have nested RecyclerViews just fine. – IgorGanapolsky Sep 28 '15 at 17:40
  • This answer is now outdated. We have things like NestedScrollView that allow for nested scrollable views. Nested RecyclerViews also now work. – Corey Horn Feb 14 '17 at 19:35
6

Add this line to your RecyclerView xml view:

android:nestedScrollingEnabled="false"

And your RecyclerView will be smoothly scrolled with flexible height.

Hope it helps.

SerjantArbuz
  • 982
  • 1
  • 12
  • 16
iDeveloper
  • 1,699
  • 22
  • 47
5

It seems that NestedScrollView does solve the problem.

I've tested using this layout:

<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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/dummy_text" />

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp">

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

        </android.support.v7.widget.CardView>

    </LinearLayout>

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

And it works without issues.

SerjantArbuz
  • 982
  • 1
  • 12
  • 16
royB
  • 12,779
  • 15
  • 58
  • 80
  • Bro, Still have the Same Problem After changed into NestedScrollview from the Scrollview. – MohanRaj S Jul 29 '16 at 07:05
  • mmm...can you share some code...I'm having zero issues but u can never know with this kind of issues – royB Jul 29 '16 at 15:35
  • 4
    using this code calls onBindView for all the items in the list even if those items are not visible in the list. This defeats the purpose of recyclerview. – thedarkpassenger Jun 04 '18 at 17:36
3

I was having the same problem. That's what i tried and it works. I am sharing my xml and java code. Hope this will help someone.

Here is the xml

<?xml version="1.0" encoding="utf-8"?>

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

        <ImageView
            android:id="@+id/iv_thumbnail"
            android:layout_width="match_parent"
            android:layout_height="200dp" />

        <TextView
            android:id="@+id/tv_description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Description" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Buy" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Reviews" />
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rc_reviews"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        </android.support.v7.widget.RecyclerView>

    </LinearLayout>
</NestedScrollView >

Here is the related java code. It works like a charm.

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setNestedScrollingEnabled(false);
Zeeshan Shabbir
  • 6,704
  • 4
  • 38
  • 74
2

I used CustomLayoutManager to disable RecyclerView Scrolling. Also don't use Recycler View as WrapContent, use it as 0dp, Weight=1

public class CustomLayoutManager extends LinearLayoutManager {
    private boolean isScrollEnabled;

    // orientation should be LinearLayoutManager.VERTICAL or HORIZONTAL
    public CustomLayoutManager(Context context, int orientation, boolean isScrollEnabled) {
        super(context, orientation, false);
        this.isScrollEnabled = isScrollEnabled;
    }

    @Override
    public boolean canScrollVertically() {
        //Similarly you can customize "canScrollHorizontally()" for managing horizontal scroll
        return isScrollEnabled && super.canScrollVertically();
    }
}

Use CustomLayoutManager in RecyclerView:

CustomLayoutManager mLayoutManager = new CustomLayoutManager(getBaseActivity(), CustomLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(mLayoutManager);
        ((DefaultItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); 
        recyclerView.setAdapter(statsAdapter);

UI XML:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background_main"
    android:fillViewport="false">


    <LinearLayout
        android:id="@+id/contParentLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <edu.aku.family_hifazat.libraries.mpchart.charts.PieChart
                android:id="@+id/chart1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/x20dp"
                android:minHeight="@dimen/x300dp">

            </edu.aku.family_hifazat.libraries.mpchart.charts.PieChart>


        </FrameLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">


        </android.support.v7.widget.RecyclerView>


    </LinearLayout>


</ScrollView>
Hamza Khan
  • 1,433
  • 13
  • 19
1

Actually the main purpose of the RecyclerView is to compensate for ListView and ScrollView. Instead of doing what you're actually doing: Having a RecyclerView in a ScrollView, I would suggest having only a RecyclerView that can handle many types of children.

Samer
  • 536
  • 3
  • 5
  • 1
    This would work only provided that your children can be garbaged collected as soon as you scroll them out of view. If you have children that are mapFragments or streetviews, it doesn't make sense as they are forced to reload each time they scroll off the recyclerview. Embedded them into a scrollview and then generating a recyclerview at the bottom makes more sense then. – Simon Sep 28 '15 at 17:23
  • 1
    @Simon there's handy setIsRecyclable() in ViewHolder – ernazm Sep 30 '15 at 13:07
  • RecyclerView replaces ListView it is not meant to replace ScrollView. – cyroxis May 26 '16 at 12:05
  • This comment deserves better. It is a solution, and even better than the accepted one. – Kai Wang May 09 '17 at 17:42
1

This does the trick:

recyclerView.setNestedScrollingEnabled(false);
petezurich
  • 9,280
  • 9
  • 43
  • 57
Shailendra Madda
  • 20,649
  • 15
  • 100
  • 138
0

First you should use NestedScrollView instead of ScrollView and put the RecyclerView inside NestedScrollView.

Use Custom layout class to measure the height and width of screen:

public class CustomLinearLayoutManager extends LinearLayoutManager {

public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {
        if (getOrientation() == HORIZONTAL) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    heightSpec,
                    mMeasuredDimension);

            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            measureScrapChild(recycler, i,
                    widthSpec,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }
    switch (widthMode) {
        case View.MeasureSpec.EXACTLY:
            width = widthSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    switch (heightMode) {
        case View.MeasureSpec.EXACTLY:
            height = heightSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    setMeasuredDimension(width, height);
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {
    View view = recycler.getViewForPosition(position);
    recycler.bindViewToPosition(view, position);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                getPaddingLeft() + getPaddingRight(), p.width);
        int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                getPaddingTop() + getPaddingBottom(), p.height);
        view.measure(childWidthSpec, childHeightSpec);
        measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
        measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
        recycler.recycleView(view);
    }
}
}

And implement below code in the activity/fragment of RecyclerView:

 final CustomLinearLayoutManager layoutManager = new CustomLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

    recyclerView.setLayoutManager(layoutManager);
    recyclerView.setAdapter(mAdapter);

    recyclerView.setNestedScrollingEnabled(false); // Disables scrolling for RecyclerView, CustomLinearLayoutManager used instead of MyLinearLayoutManager
    recyclerView.setHasFixedSize(false);

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

            int visibleItemCount = layoutManager.getChildCount();
            int totalItemCount = layoutManager.getItemCount();
            int lastVisibleItemPos = layoutManager.findLastVisibleItemPosition();
            Log.i("getChildCount", String.valueOf(visibleItemCount));
            Log.i("getItemCount", String.valueOf(totalItemCount));
            Log.i("lastVisibleItemPos", String.valueOf(lastVisibleItemPos));
            if ((visibleItemCount + lastVisibleItemPos) >= totalItemCount) {
                Log.i("LOG", "Last Item Reached!");
            }
        }
    });
Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
Soni Kumar
  • 1,533
  • 1
  • 11
  • 12
  • It gets me an exception of `IndexOutOfBoundsException: Invalid item position 0(0) ... CustomLinearLayoutManager.measureScrapChild(CustomLinearLayoutManager.java:68)` . That's even though the items count isn't 0. Can you please post this sample on github? – android developer Jul 05 '23 at 20:20
0

If RecyclerView showing only one row inside ScrollView. You just need to set height of your row to android:layout_height="wrap_content".

SANAT
  • 8,489
  • 55
  • 66
0

You can also override LinearLayoutManager to make RecyclerView roll smoothly:

@Override
public boolean canScrollVertically(){
    return false;
}
SerjantArbuz
  • 982
  • 1
  • 12
  • 16
Fred
  • 1
  • 1
0

Sorry being late to the party, but it seems like there is another solution which works perfectly for the case you mentioned.

If you use a recycler view inside a recycler view, it seems to work perfectly fine. I have personally tried and used it, and it seems to give no slowness and no jerkyness at all. Now I am not sure if this is a good practice or not, but nesting multiple recycler views , even nested scroll view slows down. But this seems to work nicely. Please give it a try. I am sure nesting is going to be perfectly fine with this.

0

Another approach to address the issue is to use ConstraintLayout inside ScrollView:

<ScrollView>
  <ConstraintLayout> (this is the only child of ScrollView)
    <...Some Views...>
    <RecyclerView> (layout_height=wrap_content)
    <...Some Other Views...>

But I would still stick to the androidx.core.widget.NestedScrollView approach, proposed by Yang Peiyong.

soshial
  • 5,906
  • 6
  • 32
  • 40
0

You can try with setting recycler view Hight as wrap_content. in my case its working fine. I am trying with 2 different recycler view in scroll view

0

The best solution is to keep multiple Views in a Single View / View Group and then keep that one view in the SrcollView. ie.

Format -

<ScrollView> 
  <Another View>
       <RecyclerView>
       <TextView>
       <And Other Views>
  </Another View>
</ScrollView>

Eg.

<ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView           
              android:text="any text"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"/>


        <TextView           
              android:text="any text"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"/>
 </ScrollView>

Another Eg. of ScrollView with multiple Views

<ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:orientation="vertical"
            android:layout_weight="1">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/imageView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#FFFFFF"
                />

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

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/CategoryItem"
                    android:textSize="20sp"
                    android:textColor="#000000"
                    />

                <TextView
                    android:textColor="#000000"
                    android:text="₹1000"
                    android:textSize="18sp"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
                <TextView
                    android:textColor="#000000"
                    android:text="so\nugh\nos\nghs\nrgh\n
                    sghs\noug\nhro\nghreo\nhgor\ngheroh\ngr\neoh\n
                    og\nhrf\ndhog\n
                    so\nugh\nos\nghs\nrgh\nsghs\noug\nhro\n
                    ghreo\nhgor\ngheroh\ngr\neoh\nog\nhrf\ndhog"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>

             </LinearLayout>

        </LinearLayout>

</ScrollView>
Harshit Jain
  • 885
  • 11
  • 15
0

For those people who trying to do it just for design purposes - leave it. Redesign your app and leave only RecyclerView. It will be better solution than doing ANY hardcode.

danyapd
  • 2,516
  • 1
  • 14
  • 23
-2

Solution which worked for me

Use NestedScrollView with height as wrap_content, and for your RecyclerView setup this:

<androidx.recyclerview.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:nestedScrollingEnabled="false"
    app:layoutManager="android.support.v7.widget.LinearLayoutManager" />

And set view holder layout params:

android:layout_width="match_parent"
android:layout_height="wrap_content"
SerjantArbuz
  • 982
  • 1
  • 12
  • 16
maruti060385
  • 707
  • 8
  • 11
  • [Some comments on a similar answer](https://stackoverflow.com/a/37337641/4515489) say disabling nested scrolling defeats the purpose of using a RecyclerView, i.e. that it won't recycle views. Not sure how to confirm that. – jk7 Oct 13 '17 at 19:14
  • 1
    Setting nestedScrollingEnabled="false" causes RecyclerView to NOT recycle its views, at least in my setup (a RecyclerView inside a NestedScrollView). Confirmed by adding a RecyclerListener to the RecyclerView and setting a breakpoint inside its onViewRecycled() method. – jk7 Oct 13 '17 at 23:38