137

I am getting a weird scrolling behavior when I add a RecyclerView inside a NestedScrollView.

What happens is that whenever the scrollview has more rows than can be shown in the screen, as soon as the activity is launched, the NestedScrollView starts with an offset from the top (image 1). If there are few items in the scroll view so that they can all be shown at once, this doesn't happen (image 2).

I am using version 23.2.0 of the support library.

Image 1: WRONG - starts with offset from the top

Image 1

Image 2: CORRECT - few items in the recycler view

Image 2

I am pasting below my layout code:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="fill_vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

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

            <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="16dp"
                android:orientation="vertical">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Title:"
                    style="@style/TextAppearance.AppCompat.Caption"/>

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="@dimen/bodyPadding"
                    style="@style/TextAppearance.AppCompat.Body1"
                    android:text="Neque porro quisquam est qui dolorem ipsum"/>

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Subtitle:"
                    style="@style/TextAppearance.AppCompat.Caption"/>

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    style="@style/TextAppearance.AppCompat.Body1"
                    android:padding="@dimen/bodyPadding"
                    android:text="Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."/>

            </LinearLayout>

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

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

Am I missing something? Does anyone have any idea how to fix this?

Update 1

It works correctly if I place the following code when initializing my Activity:

sv.post(new Runnable() {
        @Override
        public void run() {
            sv.scrollTo(0,0);
        }
});

Where sv is a reference to the NestedScrollView, however it looks like quite a hack.

Update 2

As requested, here is my adapter code:

public abstract class ArrayAdapter<T, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> {

    private List<T> mObjects;

    public ArrayAdapter(final List<T> objects) {
        mObjects = objects;
    }

    /**
     * Adds the specified object at the end of the array.
     *
     * @param object The object to add at the end of the array.
     */
    public void add(final T object) {
        mObjects.add(object);
        notifyItemInserted(getItemCount() - 1);
    }

    /**
     * Remove all elements from the list.
     */
    public void clear() {
        final int size = getItemCount();
        mObjects.clear();
        notifyItemRangeRemoved(0, size);
    }

    @Override
    public int getItemCount() {
        return mObjects.size();
    }

    public T getItem(final int position) {
        return mObjects.get(position);
    }

    public long getItemId(final int position) {
        return position;
    }

    /**
     * Returns the position of the specified item in the array.
     *
     * @param item The item to retrieve the position of.
     * @return The position of the specified item.
     */
    public int getPosition(final T item) {
        return mObjects.indexOf(item);
    }

    /**
     * Inserts the specified object at the specified index in the array.
     *
     * @param object The object to insert into the array.
     * @param index  The index at which the object must be inserted.
     */
    public void insert(final T object, int index) {
        mObjects.add(index, object);
        notifyItemInserted(index);

    }

    /**
     * Removes the specified object from the array.
     *
     * @param object The object to remove.
     */
    public void remove(T object) {
        final int position = getPosition(object);
        mObjects.remove(object);
        notifyItemRemoved(position);
    }

    /**
     * Sorts the content of this adapter using the specified comparator.
     *
     * @param comparator The comparator used to sort the objects contained in this adapter.
     */
    public void sort(Comparator<? super T> comparator) {
        Collections.sort(mObjects, comparator);
        notifyItemRangeChanged(0, getItemCount());
    }
}

And here is my ViewHolder:

public class ViewHolder extends RecyclerView.ViewHolder {
    private TextView txt;
    public ViewHolder(View itemView) {
        super(itemView);
        txt = (TextView) itemView;
    }

    public void render(String text) {
        txt.setText(text);
    }
}

And here is the layout of each item in the RecyclerView (it's just android.R.layout.simple_spinner_item - this screen is only for showing an example of this bug):

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@android:id/text1"
    style="?android:attr/spinnerItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="marquee"
    android:textAlignment="inherit"/>
Luccas Correa
  • 2,062
  • 2
  • 17
  • 19
  • tried with `android:focusableInTouchMode="false"` for `RecyclerView`? smth clearly is "forcing your layout to go to bottom... (where it start when you have 1295 items? bottom or just small top offset like first screen) – snachmsm Mar 30 '16 at 16:44
  • 1
    Set android:clipToPadding=“true” to your NestedScrollView . – natario Mar 30 '16 at 16:45
  • also: keeping scrollable view inside another same-way-scrollable view isn't very good pattern... hope you are using proper `LayoutManager` – snachmsm Mar 30 '16 at 16:48
  • Tried both of your suggestions and unfortunately neither worked. @snachmsm no matter the number of items in the recycler view, the offset is always the same. As to whether placing a RecyclerView inside a NestedScrollView is a good pattern, this has actually been recommended by a Google engineer at https://plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T – Luccas Correa Mar 30 '16 at 16:56
  • I am able to fix it by calling the NestedScrollView's post method, I added it to my question. But it feels like a nasty hack... – Luccas Correa Mar 30 '16 at 17:04
  • It is a bit hacky, maybe show us pieco of code from adapter and recycler child layout. Also `LayoutManager` used for `RecyclerView` might be helpful... (I was using same snippet other day, but found focus in child ;) ) – snachmsm Mar 30 '16 at 18:57
  • @snachmsm I added the code to my question, maybe it will help but I wonder if this is an internal bug in the implementation of NestedScrollView... – Luccas Correa Mar 30 '16 at 19:27
  • `mObjects.add(object); notifyItemInserted(getItemCount() - 1);` getItemCount should be get before add, notify only new ;) maybe this whole list redrawing (which is unnessesary, only new item should be drawn) causes this offset and scroll a bit. still: `LayoutManager` is also important if custom – snachmsm Mar 30 '16 at 19:41
  • I have exactly the same issue. Thanks for the post runnable hack. I have my layout in fragment, that is in Drawer layout. It sets the offset on every drawer close as well :-/ – Lubos Horacek Apr 24 '16 at 07:11
  • 1
    Even i have same issue when using RecyclerView inside NestedScrollView. It is a bad pattern because the recycler pattern itself won't work. All the views will be drawn at a time (since WRAP_CONTENT needs height of recycler view). There won't be any view recycling in the background, so the main purpose of recycler view itself is not working. But it is easy to manage data and draw layout by using recycler view, that's the only reason you can use this pattern. So better not to use it until unless you require it for sure. – Ashok Varma Apr 28 '16 at 06:04
  • I have an issue, where the RecycleView is inside a NestedScrollview. I can't use recycleview.scrollToPosition(X); , it just doesn't work. I tried everything in the last 6 days, but I can get over it. any suggestion? I would be very thankful ! – Karoly Jan 06 '17 at 17:59
  • I have the same issue. We are trapped at work into using RecyclerView inside a NestedScrollView. I'd just like to highlight to everyone that this completely destroys all recycling ability. All your views for the whole list will be laid out on first visiting the screen – Carson Holzheimer Jun 21 '18 at 06:59
  • Possible duplicate of [How to remove focus from RecyclerView inside ScrollView?](https://stackoverflow.com/questions/37850550/how-to-remove-focus-from-recyclerview-inside-scrollview) – lucidbrot Aug 08 '19 at 14:59

13 Answers13

259

I solved such issue by setting:

<ImageView ...
android:focusableInTouchMode="true"/>

to my view above RecyclerView (which was hidden after unwanted scroll). Try to set this property to your LinearLayout above RecyclerView or to LinearLayout which is container of RecyclerView (helped me in another case).

As I see in NestedScrollView source it tries to focus the first possible child in onRequestFocusInDescendants and if only RecyclerView is focusable it wins.

Edit (thanks to Waran): and for smooth scroll don't forget to set yourRecyclerView.setNestedScrollingEnabled(false);

Dmitry Gavrilko
  • 2,744
  • 1
  • 8
  • 5
  • 12
    By the way, you need to add yourRecyclerView.setNestedScrollingEnabled(false); in order to make scroll smooth – Waran- Jun 02 '16 at 14:19
  • Works perfectly, but you have to make sure to add the above attribute to views that are not seen after the scroll. – spectre10 Aug 02 '16 at 20:42
  • pardon me, where I should add this attribute? to all of my views except Recycle View ?? – Mahdi Dec 19 '16 at 15:21
  • 1
    @Kenji only to first view inside of LinearLayout where RecyclerView is placed. Or to that LinearLayout (RecyclerView container) itself if first change doesn't help. – Dmitry Gavrilko Dec 21 '16 at 18:02
  • 1
    @DmitryGavrilko I have an issue, where the RecycleView is inside a NestedScrollview. I can't use recycleview.scrollToPosition(X); , it just doesn't work. I tried everything in the last 6 days, but I can get over it. any suggestion? I would be very thankful ! – Karoly Jan 06 '17 at 17:57
  • 3
    You can also just set `android:focusableInTouchMode="false"`to the recyclerView so that you dont need to set true for all the other views. – Aksiom Jan 06 '17 at 21:32
  • Agree with @Aksiom. Setting recyclerView.setFocusableInTouchMode(false) worked well for me. – whizzle Mar 07 '17 at 20:04
  • 1
    Agree with Dmitry Gavrilko, worked after setting android:focusableInTouchMode="true" to the linearlayout which contains recyclerview. @Aksiom setting android:focusableInTouchMode="false" to the recyclerView doesn't worked for me. – Shendre Kiran Jul 14 '19 at 07:26
110

In your LinearLayout immediate after NestedScrollView, use android:descendantFocusability in the following way

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp"
        android:descendantFocusability="blocksDescendants">

EDIT

Since many of them getting this answer useful, will also provide explanation.

The use of descendantFocusability is given here. And as of focusableInTouchMode over here. So using blocksDescendants in descendantFocusability do not allows it's child to gain focus while touching and hence unplanned behaviour can be stopped.

As for focusInTouchMode, both AbsListView and RecyclerView calls the method setFocusableInTouchMode(true); in their constructor by default, so it is not required to use that attribute in your XML layouts.

And for NestedScrollView following method is used:

private void initScrollView() {
        mScroller = ScrollerCompat.create(getContext(), null);
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setWillNotDraw(false);
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }

Here, setFocusable() method is used instead of setFocusableInTouchMode(). But according to this post, focusableInTouchMode should be avoided unless for certain conditions as it breaks consistency with Android normal behaviour. A game is a good example of an application that can make good use of the focusable in touch mode property. MapView, if used in fullscreen as in Google Maps, is another good example of where you can use focusable in touch mode correctly.

Community
  • 1
  • 1
Jimit Patel
  • 3,985
  • 2
  • 30
  • 50
  • Thank you! It works in my case. Unfortunately android:focusableInTouchMode="true" not worked for me. – Mikhail Feb 13 '17 at 14:09
  • 1
    This worked for me. I added this line of code to the parent view of my recyclerview (in my case, it was a LinearLayout) and it worked like a charm. – amzer May 25 '17 at 04:45
  • I was using NestedScrollView followed by LinearLayout which contains RecyclerView and EditText. The scrolling behavior was fixed by using android:descendantFocusability="blocksDescendants" inside LinearLayout but EditText cannot gain focus. Any idea what is happening? – Sagar Chapagain Jun 26 '17 at 05:17
  • @SagarChapagain It's the property of `blocksDescendants` to block all descendant's focus. I am not sure, but try `beforeDescendants` – Jimit Patel Jun 26 '17 at 11:10
  • 2
    @JimitPatel my problem was solved using `recyclerView.setFocusable(false); nestedScrollView.requestFocus();` – Sagar Chapagain Jun 26 '17 at 11:26
  • What if I have touchable views in the recyclerView? The `blocksDescendants` property will avoid any click listeners triggers on them, won't it? – Leandro Temperoni Apr 29 '19 at 17:12
  • @LeandroTemperoni Actually, the one who questioned this never had any focus related elements in his code, so this is the solution. While coming to your problem, I think focus for `EditText` is the problem, but for `Button` even I am not aware of it, it should work according to me, because I was using horizontal `RecyclerView` with click on element to next screen. But with latest updates I am not sure. Try it out!! and say whether working or not. :) – Jimit Patel Apr 30 '19 at 07:38
  • This should be the accepted answer – etomun Jan 06 '21 at 04:05
  • I was experiencing something similar in a layout with an `AppBarLayout` + `CollapsingToolbarLayout` + `NestedScrollView` + `RecyclerView`. Adding `android:clipToPadding="true"` to the `NestedScrollView` and the `RecyclerView`, plus `android:descendantFocusability="blocksDescendants"` to the child viewgroup of the `NestedScrollView` and `android:nestedScrollingEnabled="false"` to the `RecyclerView` fixed all the issues. – David Miguel Mar 02 '21 at 15:42
19
android:descendantFocusability="blocksDescendants"

inside LinearLayout Worked for me .

11

I had the same issue and sovled by extending NestedScrollView and disabling focusing children. For some reason, RecyclerView always requested focus even when I just opened and closed the drawer.

public class DummyNestedScrollView extends NestedScrollView {
public DummyNestedScrollView(Context context) {
    super(context);
}

public DummyNestedScrollView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

public DummyNestedScrollView(Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

/**
 * Fixind problem with recyclerView in nested scrollview requesting focus
 * http://stackoverflow.com/questions/36314836/recycler-view-inside-nestedscrollview-causes-scroll-to-start-in-the-middle
 * @param child
 * @param focused
 */
@Override
public void requestChildFocus(View child, View focused) {
    Log.d(getClass().getSimpleName(), "Request focus");
    //super.requestChildFocus(child, focused);

}


/**
 * http://stackoverflow.com/questions/36314836/recycler-view-inside-nestedscrollview-causes-scroll-to-start-in-the-middle
 * @param direction
 * @param previouslyFocusedRect
 * @return
 */
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
    Log.d(getClass().getSimpleName(), "Request focus descendants");
    //return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
    return false;
}
}
Lubos Horacek
  • 1,484
  • 10
  • 26
  • It works but it also breaks focus concept and you can easy get situation with two focused fields on the screen. So use it only if you have no EditText inside `RecyclerView` holders. – Andrey Chernoprudov Mar 11 '20 at 12:28
5

In my case this code solves mine issue

RecyclerView recyclerView = findViewById(R.id.recyclerView);
NestedScrollView nestedScrollView= findViewById(R.id.nestedScrollView);

recyclerView.setFocusable(false);
nestedScrollView.requestFocus();

//populate recyclerview here

My layout contains a parent layout as NestedScrollView which has a child LinearLayout. The LinearLayout has orientation "vertical" and childs RecyclerView and EditText. Reference

Sagar Chapagain
  • 1,476
  • 2
  • 14
  • 21
4

Just add android:descendantFocusability="blocksDescendants" on the ViewGroup inside the NestedScrollView.

2

I have two guesses.

First:Try putting this line on your NestedScrollView

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Second: Use

<android.support.design.widget.CoordinatorLayout

as your parent view Like this

<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="fill_vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

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

        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                      android:layout_width="match_parent"
                      android:layout_height="wrap_content"
                      android:orientation="vertical"
                      android:padding="16dp">

            <TextView
                style="@style/TextAppearance.AppCompat.Caption"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Title:"/>

            <TextView
                style="@style/TextAppearance.AppCompat.Body1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="@dimen/bodyPadding"
                android:text="Neque porro quisquam est qui dolorem ipsum"/>

            <TextView
                style="@style/TextAppearance.AppCompat.Caption"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Subtitle:"/>

            <TextView
                style="@style/TextAppearance.AppCompat.Body1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="@dimen/bodyPadding"
                android:text="Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."/>

        </LinearLayout>

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

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

My last possible solution. I swear :)

Andre Haueisen
  • 242
  • 7
  • 16
2

This problem arrives due to recycle view Focus.

Automatically all focus gone to recycle view if its size extended the size of screen.

adding android:focusableInTouchMode="true" to first ChildView like TextView, Button and so on(Not on ViewGroup like Linear, Relative and So on) make sense to solve the problem but API Level 25 and above solution doesn't work.

Just add these 2 line in your ChildView like TextView, Button and So on (Not on ViewGroup like Linear, Relative and So on)

 android:focusableInTouchMode="true"
 android:focusable="true"

I just faced this problem on API level 25. I hope other people don't waste time in this.

For Smooth Scrolling On RecycleView add this line

 android:nestedScrollingEnabled="false"

but adding this attiributes only works with API level 21 or above. If you want that smoothing scrolling work on below API level 25 then add this line in your class

 mList = findViewById(R.id.recycle_list);
 ViewCompat.setNestedScrollingEnabled(mList, false);
sushildlh
  • 8,334
  • 3
  • 29
  • 74
0

In Java code, after initializing your recyclerView and setting the adapter, add this line:

recyclerView.setNestedScrollingEnabled(false)

You can also try to wrap the layout withing a relativeLayout so that the views stay at the same position but recyclerView (which scroll) is first in xml hierarchy. The last suggestion a desperate attempt:p

Däñish Shärmà
  • 2,743
  • 2
  • 22
  • 41
johnny_crq
  • 3,993
  • 5
  • 32
  • 61
0

As I am late in responding, but may can help someone else. Just use the below or higher version in your app level build.gradle and the issue is removed.

compile com.android.support:recyclerview-v7:23.2.1
Amit
  • 479
  • 4
  • 7
0

to scroll to the top, just call this in setcontentview:

scrollView.SmoothScrollTo(0, 0);
FelixSFD
  • 5,456
  • 10
  • 40
  • 106
0

Please do not use

android:descendantFocusability="blocksDescendants"

because the ViewGroup will block its descendants from receiving focus. This is an accessibility issue.

If you want to avoid the nestedscrollview to receive focus, you can add a dummy layout which is focusable above the nestedscrollview.

   <View
       android:layout_width="0dp"
       android:layout_height="0dp"
       android:focusable="true"
       android:focusableInTouchMode="true" />
Anju
  • 8,924
  • 14
  • 50
  • 86
0

In My case It was scrolling and focusing to recyclerview due to, I had added android:layout_gravity="center" in parent linearlayout. After removing it works properly.