RecyclerView inside ScrollView is not working
Asked Answered
F

26

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.
Fresher answered 22/11, 2014 at 22:7 Comment(11)
checkout this approach: https://mcmap.net/q/37081/-how-can-i-put-a-listview-into-a-scrollview-without-it-collapsingPlug
a simple solution in many of these cases is to use NestedScrollView instead, as it handles a lot of scrolling issuesDeuteron
Richard gave you the answer in February. Use a NestedScrollView instead of a ScrollView. That's exactly what it's for.Moreland
Doesn't change a thing for me.Endo
For future reference, if anybody is experiencing similar issue only marshmallow/nougat (API 23, 24) devices, check my workaround at https://mcmap.net/q/93220/-recyclerview-inside-scrollview-is-not-workingSwallowtailed
RecyclerView doesn't recycle views in this layout hierarchy. You can verify by adding hundreds of items and checking the jank.Sendal
Try disabling the touch of recycler view when scrollview is being touched. https://mcmap.net/q/94392/-scrollview-inside-recyclerview-won-39-t-scrollEmbody
@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.By
@By 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.Deuteron
@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 answerBy
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.Deuteron
M
819

use NestedScrollView instead of ScrollView

Please go through NestedScrollView reference document for more information.

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

Mantel answered 20/5, 2016 at 5:42 Comment(15)
Works with : android.support.v4.widget.NestedScrollViewInborn
keep android:layout_height="wrap_content" for the layout inflated for ViewHolderMenado
In a complex layout NestedScrollView lags for me, unlike the ScrollView. Searching for a solution without using NestedScrollViewCarabao
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?Yeomanly
For below Lollipop versions: https://mcmap.net/q/94393/-recyclerview-inside-nested-scrollview-scroll-but-does-not-fast-scroll-like-normal-recyclerview-or-nested-scrollviewHippomenes
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 recyclerviewCaress
Also you can add android:nestedScrollingEnabled="false" to XML instead of recyclerView.setNestedScrollingEnabled(false);Parallelize
this worked for me but I have expandable items in recyclerview & the last item expands below visible screen . this wasn't the case with scrollViewFeminize
It worked for me but keep in mind that the items inside recyclerView are not getting recycled.Broderickbrodeur
it only for api level 21, how about level 19?Geomancer
For those who stumble onto this answer and did everything right. hasFixedSize must be set to false on recyclerview for this to work.Crevice
@NandaZ, ViewCompat.setNestedScrollingEnabled(recyclerView, false).Tenth
@Thijs, setHasFixedSize(true) works right in my case.Tenth
The correct package to use is now androidx.core.widget.NestedScrollViewRussell
This doesn't work well at all. Suppose you have 1000 items on the RecyclerView, it will create&bind them all right away...Cofield
S
120

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

Swallowtailed answered 17/8, 2016 at 11:29 Comment(14)
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.Abhorrence
Is this method recycle views? if we have around hundreds of object to be recycle. This is hack not solution.Porett
Yes, @Porett 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 :-)Swallowtailed
perfect.solved my problem in adnroid marshmallow and upper. ;)Poleaxe
I think your solution is better but when I removed "setHasFixedSize(true);" it works more betterLoquitur
if I use this solution, then onBindView is getting called for all the items in the list, which is not the usecase of recyclerview.Kaleighkalends
android:descendantFocusability="blocksDescendants" helped meLian
Please don't ... check your memory usage. This hack is killing what "recycler" means.Sumner
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.Swallowtailed
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 OutOfMemoryErrorGeomancer
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.Swallowtailed
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.Hoyos
@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.Severally
Btw, instead of RelativeLayout I have recently tried FrameLayout that also worked! :DSwallowtailed
L
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;
Lashawn answered 13/2, 2015 at 11:8 Comment(13)
How to get adapterItemSize in recyclerview any idea?Coryphaeus
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.Sedillo
It works like a charm! Little correction it should be : int viewHeight = adapterItemSize * adapterData.size(); recyclerView.getLayoutParams().height = viewHeight;Dionnadionne
How to find out adapterItemSize?Verbose
@JoakimEngstrom What is the adapterItemSize variable?Dorisdorisa
it will work.but it still consumes some event .not good for performanceGuadalajara
when i put recyclerview inside scroll view, my adapter is never call....any suggestion?Yaakov
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.Lashawn
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.Question
nested scroll view is made for such a purpose already. this recommendations is not recommended?Verdaverdant
@MasoudDadashi Yes, you are right, this answer is not the best answer anymore.Lashawn
using simple wrap_content as layout_height worked for me. No need of calculating the height.Openhearth
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 thatStrophe
E
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) {

    }
});
Earthward answered 24/4, 2015 at 9:2 Comment(5)
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 tipHideandseek
it worked for me too, just be sure to use getParent on a direct child of the scrollviewStratocracy
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.Hower
I also needed recyclerView.setNestedScrollingEnabled(false);Nous
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!Setiform
I
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

Ineducation answered 25/2, 2016 at 12:4 Comment(3)
doesn't fling properly too (23.4.0)Sequacious
@Sequacious 23.4.0 have some issues code.google.com/p/android/issues/detail?id=210085#makechanges , use 23.2.1 insteadAmanda
doesnt even fling properly on 25.0.1Potto
I
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.

Inanimate answered 3/11, 2015 at 22:7 Comment(1)
Couldn't you call setNestedScrollingEnabled(false) on the recyclerview?Examinee
E
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.

Emaciated answered 17/1, 2016 at 15:43 Comment(3)
Tested working fine using v23.2.1 . Was using it for adding layout above recyclerview.Caird
just the RecyclerView scrolling and the ScrollView not scrollingAmanda
Doesn't work , as it creates&binds to all items in the RecyclerViewCofield
W
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

Woolly answered 13/9, 2018 at 8:2 Comment(3)
Show how can I make RecyclerView recycle even inside ScollView? Thanks!Procure
@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)Woolly
you can give the recycler view a set heightWoollen
Q
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");
        }
    }
}
Quahog answered 5/11, 2015 at 10:56 Comment(1)
How can i call this class when we set the data to adapter?Congressman
F
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);
    
Fistulous answered 21/9, 2017 at 6:16 Comment(2)
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?Kaleighkalends
You just need to give PaddingBottom into LinearLayout that is inside nestedScrollView - @KaleighkalendsTasha
B
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

Benzedrine answered 25/11, 2014 at 6:14 Comment(4)
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 commentSanitarian
make it a single recyclerView and put your views as items for that recyclerBenzedrine
That is nonsense. You can have nested RecyclerViews just fine.Dorisdorisa
This answer is now outdated. We have things like NestedScrollView that allow for nested scrollable views. Nested RecyclerViews also now work.Milliliter
P
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.

Pilaf answered 21/12, 2016 at 17:9 Comment(0)
F
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.

Fresher answered 27/5, 2016 at 4:45 Comment(3)
Bro, Still have the Same Problem After changed into NestedScrollview from the Scrollview.Legate
mmm...can you share some code...I'm having zero issues but u can never know with this kind of issuesFresher
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.Kaleighkalends
E
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);
Emitter answered 14/11, 2016 at 5:7 Comment(4)
What about layouts below RecyclerView?Sentience
that works as well. You just need to use. NestedScrollView instead of scroll viewEmitter
Yes, only NestedScrollView. But solution with NestedScrollView+RecycleView call very slow populations of RecyclerView (I think for calcutaion of Y-position of first view after RecyclerView)Sentience
Doesn't work , as it creates&binds to all items in the RecyclerViewCofield
P
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>
Pinot answered 31/12, 2018 at 11:3 Comment(0)
C
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.

Colyer answered 27/8, 2015 at 5:41 Comment(4)
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.Annmarieannnora
@Annmarieannnora there's handy setIsRecyclable() in ViewHolderCreative
RecyclerView replaces ListView it is not meant to replace ScrollView.Billetdoux
This comment deserves better. It is a solution, and even better than the accepted one.Dysphagia
H
1

This does the trick:

recyclerView.setNestedScrollingEnabled(false);
Hemidemisemiquaver answered 17/3, 2018 at 14:0 Comment(0)
R
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!");
            }
        }
    });
Reaction answered 8/8, 2016 at 6:18 Comment(1)
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?Cofield
W
0

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

Winnick answered 18/11, 2016 at 5:21 Comment(0)
H
0

You can also override LinearLayoutManager to make RecyclerView roll smoothly:

@Override
public boolean canScrollVertically(){
    return false;
}
Hulbig answered 12/3, 2017 at 6:32 Comment(0)
S
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.

Sanyu answered 30/6, 2017 at 6:33 Comment(0)
T
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.

Taciturnity answered 15/4, 2020 at 18:33 Comment(0)
S
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

Slotnick answered 8/10, 2020 at 11:34 Comment(0)
H
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>
Hic answered 30/10, 2020 at 10:49 Comment(0)
G
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.

Gally answered 10/11, 2020 at 10:6 Comment(0)
P
-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"
Potto answered 2/8, 2017 at 16:46 Comment(2)
Some comments on a similar answer 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.Physiological
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.Physiological

© 2022 - 2024 — McMap. All rights reserved.