139

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"/>
4

13 回答 13

261

我通过设置解决了这样的问题:

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

到我在 RecyclerView 上方的视图(在不需要的滚动后被隐藏)。尝试将此属性设置为 RecyclerView 上方的 LinearLayout 或作为 RecyclerView 容器的 LinearLayout(在另一种情况下帮助了我)。

正如我在 NestedScrollView 源代码中看到的那样,它试图将第一个可能的孩子集中在 onRequestFocusInDescendants 中,如果只有 RecyclerView 是可聚焦的,它就会获胜。

编辑(感谢 Waran):为了平滑滚动不要忘记设置yourRecyclerView.setNestedScrollingEnabled(false);

于 2016-05-18T00:28:51.570 回答
110

在您的LinearLayout紧接之后NestedScrollViewandroid:descendantFocusability按以下方式使用

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

编辑

由于他们中的许多人对此答案很有用,因此也会提供解释。

这里descendantFocusability给出了使用。从这里开始。所以使用in不允许它的孩子在触摸时获得焦点,因此可以停止计划外的行为。focusableInTouchModeblocksDescendantsdescendantFocusability

至于和focusInTouchMode,默认情况下都会在其构造函数中调用该方法,因此不需要在 XML 布局中使用该属性。AbsListViewRecyclerViewsetFocusableInTouchMode(true);

并使用NestedScrollView以下方法:

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

在这里,setFocusable()使用方法而不是setFocusableInTouchMode(). 但是根据这篇文章focusableInTouchMode除非在某些情况下,否则应该避免,因为它破坏了与 Android 正常行为的一致性。游戏是一个很好的应用程序示例,它可以很好地利用触控模式中的可聚焦属性。MapView,如果在谷歌地图中全屏使用,是另一个很好的例子,说明你可以在触摸模式下正确使用可聚焦。

于 2016-11-07T20:29:46.407 回答
19
android:descendantFocusability="blocksDescendants"

在 LinearLayout 里面为我工作。

于 2017-03-15T07:08:31.407 回答
11

我遇到了同样的问题,并通过扩展 NestedScrollView 和禁用聚焦子级来解决。出于某种原因,即使我刚刚打开和关闭抽屉,RecyclerView 也总是请求焦点。

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;
}
}
于 2016-04-25T13:02:18.803 回答
5

就我而言,这段代码解决了我的问题

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

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

//populate recyclerview here

我的布局包含一个作为 NestedScrollView 的父布局,它有一个子 LinearLayout。LinearLayout 具有“垂直”方向和子 RecyclerView 和 EditText。参考

于 2017-06-26T10:20:05.613 回答
4

只需android:descendantFocusability="blocksDescendants"在 NestedScrollView 中添加 ViewGroup。

于 2019-10-03T00:47:22.047 回答
2

由于回收视图焦点,此问题出现。

如果其尺寸扩大了屏幕尺寸,则自动将所有焦点转到回收视图。

添加android:focusableInTouchMode="true"到第一个 ChildView like TextViewButton依此类推(不在ViewGrouplike Linear,依此类推Relative)对解决问题有意义,但 API 级别 25 及以上的解决方案不起作用。

只需将这两行添加到您的 ChildView likeTextView中,依此Button类推(不在ViewGrouplikeLinear中,Relative依此类推)

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

我刚刚在 API 级别 25 上遇到了这个问题。我希望其他人不要在这上面浪费时间。

对于 RecycleView 上的平滑滚动添加此行

 android:nestedScrollingEnabled="false"

但添加此属性仅适用于 API 级别 21 或更高级别。如果您希望在 API 级别 25 以下进行平滑滚动工作,请在您的类中添加此行

 mList = findViewById(R.id.recycle_list);
 ViewCompat.setNestedScrollingEnabled(mList, false);
于 2018-12-12T10:07:51.500 回答
2

我有两个猜测。

第一:试着把这条线放在你的 NestedScrollView

app:layout_behavior="@string/appbar_scrolling_view_behavior"

第二:使用

<android.support.design.widget.CoordinatorLayout

作为你的父母视图 像这样

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

我最后可能的解决方案。我发誓 :)

于 2016-03-30T17:11:38.177 回答
0

在 Java 代码中,初始化 recyclerView 并设置适配器后,添加以下行:

recyclerView.setNestedScrollingEnabled(false)

您还可以尝试使用 relativeLayout 包装布局,以便视图保持在同一位置,但 recyclerView (滚动)在 xml 层次结构中位于首位。最后一个建议是绝望的尝试:p

于 2016-03-30T21:14:57.663 回答
0

请不要使用

android:descendantFocusability="blocksDescendants"

因为 ViewGroup 将阻止其后代接收焦点。这是一个可访问性问题。

如果你想避免nestedscrollview接收焦点,你可以在nestedscrollview.

   <View
       android:layout_width="0dp"
       android:layout_height="0dp"
       android:focusable="true"
       android:focusableInTouchMode="true" />
于 2021-03-04T07:29:29.570 回答
0

在我的情况下,它正在滚动并专注于 recyclerview,因为我在父线性布局中添加了 android:layout_gravity="center"。删除后可以正常工作。

于 2021-03-08T05:19:57.037 回答
-1

滚动到顶部,只需调用它setcontentview

scrollView.SmoothScrollTo(0, 0);
于 2017-02-20T11:55:42.457 回答
-1

因为我回复晚了,但可能可以帮助其他人。只需在您的应用级别 build.gradle 中使用以下或更高版本即可解决问题。

compile com.android.support:recyclerview-v7:23.2.1
于 2017-01-16T06:37:24.923 回答