ChatGPT解决这个技术问题 Extra ChatGPT

RecyclerView inside ScrollView is not working

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:

card view inside the ScrollView (now ScrollView contains RecyclerView) - can see the card up until the RecyclerView 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

checkout this approach: stackoverflow.com/a/21878703/684582
a simple solution in many of these cases is to use NestedScrollView instead, as it handles a lot of scrolling issues
Richard gave you the answer in February. Use a NestedScrollView instead of a ScrollView. That's exactly what it's for.
Doesn't change a thing for me.
For future reference, if anybody is experiencing similar issue only marshmallow/nougat (API 23, 24) devices, check my workaround at stackoverflow.com/a/38995399/132121

c
cascal

use NestedScrollView instead of ScrollView

Please go through NestedScrollView reference document for more information.

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


Works with : android.support.v4.widget.NestedScrollView
keep android:layout_height="wrap_content" for the layout inflated for ViewHolder
In a complex layout NestedScrollView lags for me, unlike the ScrollView. Searching for a solution without using NestedScrollView
Also you can add android:nestedScrollingEnabled="false" to XML instead of recyclerView.setNestedScrollingEnabled(false);
It worked for me but keep in mind that the items inside recyclerView are not getting recycled.
M
Mehdi Dehghani

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:

https://github.com/amardeshbd/android-recycler-view-wrap-content/raw/master/web-resources/RecyclerView-wrap_content-demo-screen-cash.gif


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.
Is this method recycle views? if we have around hundreds of object to be recycle. This is hack not solution.
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 :-)
if I use this solution, then onBindView is getting called for all the items in the list, which is not the usecase of recyclerview.
You're genius! Awesome.
O
OWADVL

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;

How to get adapterItemSize in recyclerview any idea?
It works like a charm! Little correction it should be : int viewHeight = adapterItemSize * adapterData.size(); recyclerView.getLayoutParams().height = viewHeight;
How to find out adapterItemSize?
@JoakimEngstrom What is the adapterItemSize variable?
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.
r
rsicarelli

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) {

    }
});

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
it worked for me too, just be sure to use getParent on a direct child of the scrollview
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.
I also needed recyclerView.setNestedScrollingEnabled(false);
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!
V
Vadim Kotov

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


doesn't fling properly too (23.4.0)
@behelit 23.4.0 have some issues code.google.com/p/android/issues/detail?id=210085#makechanges , use 23.2.1 instead
doesnt even fling properly on 25.0.1
j
jcady

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: https://github.com/serso/android-linear-layout-manager/blob/master/lib/src/main/java/org/solovyev/android/views/llm/LinearLayoutManager.java

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.


Couldn't you call setNestedScrollingEnabled(false) on the recyclerview?
T
Tunaki

For ScrollView, you could use fillViewport=true and make layout_height="match_parent" as below and put recycler view 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.


Tested working fine using v23.2.1 . Was using it for adding layout above recyclerview.
just the RecyclerView scrolling and the ScrollView not scrolling
V
Vasily Kabunov

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();

    if (exactWidth && exactHeight) {
        // in case of exact calculations for both dimensions let's use default "onMeasure" implementation
        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");
    }
}
}

How can i call this class when we set the data to adapter?
R
Ranjithkumar

Try this. Very late answer. But surely help anyone in future.

Set your Scrollview to NestedScrollView

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

In your Recyclerview

recyclerView.setNestedScrollingEnabled(false); 
recyclerView.setHasFixedSize(false);

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?
You just need to give PaddingBottom into LinearLayout that is inside nestedScrollView - @thedarkpassenger
L
Linh

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


Show how can I make RecyclerView recycle even inside ScollView? Thanks!
@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)
you can give the recycler view a set height
C
Community

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


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
make it a single recyclerView and put your views as items for that recycler
That is nonsense. You can have nested RecyclerViews just fine.
This answer is now outdated. We have things like NestedScrollView that allow for nested scrollable views. Nested RecyclerViews also now work.
r
royB

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>

And it works without issues


Bro, Still have the Same Problem After changed into NestedScrollview from the Scrollview.
mmm...can you share some code...I'm having zero issues but u can never know with this kind of issues
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.
i
iDeveloper

You can use this way either :

Add this line to your recyclerView xml view :

        android:nestedScrollingEnabled="false"

try it ,recyclerview will be smoothly scrolled with flexible height

hope this helped .


H
Hamza Khan

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>

Z
Zeeshan Shabbir

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);

What about layouts below RecyclerView?
that works as well. You just need to use. NestedScrollView instead of scroll view
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)
S
Samer

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.


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 there's handy setIsRecyclable() in ViewHolder
RecyclerView replaces ListView it is not meant to replace ScrollView.
This comment deserves better. It is a solution, and even better than the accepted one.
p
petezurich

This does the trick:

recyclerView.setNestedScrollingEnabled(false);

V
Vasily Kabunov

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!");
            }
        }
    });

S
SANAT

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


F
Fred

you can also override LinearLayoutManager to make recyclerview roll smoothly

@Override
    public boolean canScrollVertically(){
        return false;
    }

S
Shailendra Pundhir

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.


s
soshial

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.


h
harsh patel

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


H
Harshit Jain

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>

d
danyapd

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.


a
amit singh

**Solution which worked for me Use NestedScrollView with height as wrap_content

<br> RecyclerView 
                android:layout_width="match_parent"<br>
                android:layout_height="wrap_content"<br>
                android:nestedScrollingEnabled="false"<br>
                  app:layoutManager="android.support.v7.widget.LinearLayoutManager"
                tools:targetApi="lollipop"<br><br> and view holder layout 
 <br> android:layout_width="match_parent"<br>
        android:layout_height="wrap_content"

//Your row content goes here


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.
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.