NestedScrollView中的Recycler视图导致滚动从中间开始


129

当我在NestedScrollView内添加RecyclerView时,出现奇怪的滚动行为。

发生的情况是,每当滚动视图的行数超过屏幕显示的行数时,活动启动后,NestedScrollView就会从顶部开始偏移(图1)。如果滚动视图中的项目很少,因此可以一次显示所有项目,则不会发生这种情况(图2)。

我正在使用支持库的23.2.0版本。

图片1:错误-从顶部偏移开始

图片1

图片2:正确-回收者视图中的物品很少

图片2

我要粘贴下面的布局代码:

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

我想念什么吗?有谁知道如何解决这个问题?

更新1

如果在初始化我的活动时放置以下代码,它将正常工作:

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

sv是对NestedScrollView的引用,但是看起来很hack。

更新2

根据要求,这是我的适配器代码:

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

这是我的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);
    }
}

这是RecyclerView中每个项目的布局(仅android.R.layout.simple_spinner_item-此屏幕仅用于显示此错误的示例):

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

尝试android:focusableInTouchMode="false"RecyclerView吗?smth显然是“强制布局从底部开始((当您有1295个项目时从哪里开始?是底部还是只是第一个屏幕一样小的顶部偏移量)?”)
snachmsm

将android:clipToPadding =“ true”设置为NestedScrollView。
natario

另外:将滚动视图保留在另一个相同的可滚动视图中不是很好的模式...希望您使用的是正确的LayoutManager
snachmsm

尝试了您的两个建议,很遗憾,两个建议都没有奏效。@snachmsm无论回收者视图中的项目数如何,偏移量始终相同。至于将RecyclerView放在NestedScrollView内是否是一个好模式,实际上是Google工程师在plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa

1
甚至我在NestedScrollView中使用RecyclerView时也遇到相同的问题。这是一个错误的模式,因为回收者模式本身将无法工作。所有视图将一次绘制(因为WRAP_CONTENT需要高度回收器视图)。后台将没有任何视图回收,因此回收器视图本身的主要目的不起作用。但是通过使用回收站视图来管理数据和绘制布局很容易,这是可以使用此模式的唯一原因。因此,除非您确实需要,否则最好不要使用它。
Ashok Varma

Answers:


253

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

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

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

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

编辑(感谢Waran):为了流畅滚动,请不要忘记设置 yourRecyclerView.setNestedScrollingEnabled(false);


12
顺便说一句,您需要添加yourRecyclerView.setNestedScrollingEnabled(false);。为了使滚动平滑
Waran-

1
@Kenji仅适用于放置RecyclerView的LinearLayout内部的第一个视图。如果第一次更改没有帮助,请转到该LinearLayout(RecyclerView容器)本身。
德米特里·加夫里科

1
@DmitryGavrilko我有一个问题,其中RecycleView在NestedScrollview内。我不能使用recycleview.scrollToPosition(X); ,它根本不起作用。我在过去6天内尝试了所有内容,但可以克服。有什么建议吗?我将非常感谢!
Karoly

3
您也可以只将其设置android:focusableInTouchMode="false"为recyclerView,这样就无需为所有其他视图设置true。
阿克西姆(Aksiom)

1
同意Dmitry Gavrilko的观点,他在将android:focusableInTouchMode =“ true”设置为包含recyclerview的linearlayout后工作。@Aksiom设置android:focusableInTouchMode =“ false”到recyclerView对我不起作用。
希尔德·基兰

103

在您使用LinearLayout之后NestedScrollView,请android: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,默认情况下,AbsListView和都在其构造函数中RecyclerView调用方法setFocusableInTouchMode(true);,因此不需要在XML布局中使用该属性。

并使用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()method代替setFocusableInTouchMode()。但是根据这篇文章focusableInTouchMode除非在某些情况下,否则应避免使用它,因为它破坏了与Android正常行为的一致性。游戏是可以充分利用可触摸模式属性的应用程序的很好示例。如果在Google地图中以全屏方式使用MapView,则是另一个可以在触摸模式下正确使用可聚焦的好例子。


谢谢!在我的情况下有效。不幸的是android:focusableInTouchMode =“ true”对我不起作用。
米哈伊尔(Mikhail)

1
这对我有用。我将这行代码添加到了recyclerview的父视图中(在我的情况下,这是一个LinearLayout),它的工作就像一个魅力。
amzer

我使用的是NestedScrollView,然后使用LinearLayout,其中包含RecyclerView和EditText。通过在LinearLayout中使用android:descendantFocusability =“ blocksDescendants”固定了滚动行为,但EditText无法获得焦点。知道发生了什么吗?
Sagar Chapagain

@SagarChapagain的属性是blocksDescendants阻止所有后代的焦点。我不确定,但是请尝试beforeDescendants
Jimit Patel

2
@JimitPatel我的问题已使用recyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain

16
android:descendantFocusability="blocksDescendants"

在LinearLayout内部为我工作。


6
但这是否意味着对内部视图的关注将被阻止,并且用户将无法访问它们?
Android开发人员

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

它有效,但也打破了焦点概念,您可以通过屏幕上的两个焦点字段轻松获得情况。因此,仅当RecyclerView所有者内部没有EditText时才使用它。
Andrey Chernoprudov

4

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

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子级。参考


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>

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


并将CoordinatorLayout添加为父视图也无法正常工作...不管怎样,谢谢
Luccas Correa 2016年

2

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

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

添加android:focusableInTouchMode="true"到第一个像这样的ChildView中TextViewButton以此类推(不像ViewGrouplike LinearRelative等等)可以解决该问题,但是API Level 25及更高版本的解决方案不起作用。

只需在您的ChildView中添加这两行,例如TextViewButton等等(不ViewGroup喜欢LinearRelative等等)

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

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

为了在RecycleView上平滑滚动,请添加以下行

 android:nestedScrollingEnabled="false"

但添加此服装仅适用于21级或更高级别的API。如果您希望在API级别25以下进行平滑滚动,请在您的课程中添加此行

 mList = findViewById(R.id.recycle_list);
 ViewCompat.setNestedScrollingEnabled(mList, false);

0

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

recyclerView.setNestedScrollingEnabled(false)

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


0

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

compile com.android.support:recyclerview-v7:23.2.1

0

要滚动到顶部,只需在中调用setcontentview

scrollView.SmoothScrollTo(0, 0);

这并未提供问题的答案
Umar Ata '18

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.