NestedScrollView 内的回收器视图导致滚动从中间开始
Posted
技术标签:
【中文标题】NestedScrollView 内的回收器视图导致滚动从中间开始【英文标题】:Recycler view inside NestedScrollView causes scroll to start in the middle 【发布时间】:2016-07-18 19:27:51 【问题描述】:当我在 NestedScrollView 中添加 RecyclerView 时,出现奇怪的滚动行为。
发生的情况是,每当滚动视图的行数超过屏幕上显示的行数时,一旦启动 Activity,NestedScrollView 就会从顶部偏移开始(图 1)。如果滚动视图中的项目很少,因此它们都可以一次显示,则不会发生这种情况(图 2)。
我正在使用 23.2.0 版的支持库。
图片 1:错误 - 从顶部偏移开始
图片 2:正确 - 回收站视图中的一些项目
我在我的布局代码下面粘贴:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
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_
android:layout_
android:orientation="vertical"
android:padding="10dp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:padding="16dp"
android:orientation="vertical">
<TextView
android:layout_
android:layout_
android:text="Title:"
style="@style/TextAppearance.AppCompat.Caption"/>
<TextView
android:layout_
android:layout_
android:padding="@dimen/bodyPadding"
style="@style/TextAppearance.AppCompat.Body1"
android:text="Neque porro quisquam est qui dolorem ipsum"/>
<TextView
android:layout_
android:layout_
android:text="Subtitle:"
style="@style/TextAppearance.AppCompat.Caption"/>
<TextView
android:layout_
android:layout_
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_
android:layout_ />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
我错过了什么吗?有谁知道如何解决这个问题?
更新 1
如果我在初始化我的 Activity 时放置以下代码,它可以正常工作:
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_
android:layout_
android:ellipsize="marquee"
android:textAlignment="inherit"/>
【问题讨论】:
用android:focusableInTouchMode="false"
尝试RecyclerView
? smth 显然是“强制你的布局到底部......(当你有 1295 个项目时它从哪里开始?底部或只是像第一个屏幕一样的小顶部偏移)
将 android:clipToPadding="true" 设置为 NestedScrollView 。
另外:将可滚动视图保持在另一个同向可滚动视图中并不是很好的模式...希望您使用正确的LayoutManager
尝试了您的两个建议,但不幸的是都没有奏效。 @snachmsm 无论回收器视图中的项目数量如何,偏移量始终相同。至于在 NestedScrollView 中放置 RecyclerView 是否是一个好的模式,这个其实已经被 Google 工程师推荐了plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
即使我在 NestedScrollView 中使用 RecyclerView 时也遇到了同样的问题。这是一个糟糕的模式,因为回收器模式本身不起作用。所有视图将一次绘制(因为 WRAP_CONTENT 需要回收器视图的高度)。后台不会有任何视图回收,所以回收器视图本身的主要目的是不起作用的。但是使用recycler view很容易管理数据和绘制布局,这是你可以使用这种模式的唯一原因。所以最好不要使用它,除非你确实需要它。
【参考方案1】:
我通过设置解决了这样的问题:
<ImageView ...
android:focusableInTouchMode="true"/>
我在 RecyclerView 上方的视图(在不需要的滚动后被隐藏)。尝试将此属性设置为 RecyclerView 上方的 LinearLayout 或作为 RecyclerView 容器的 LinearLayout (在另一种情况下帮助了我)。
正如我在 NestedScrollView 源代码中看到的那样,它会尝试将第一个可能的子元素集中在 onRequestFocusInDescendants 中,如果只有 RecyclerView 是可聚焦的,它就会获胜。
编辑(感谢 Waran):为了平滑滚动不要忘记设置yourRecyclerView.setNestedScrollingEnabled(false);
【讨论】:
对了,需要添加yourRecyclerView.setNestedScrollingEnabled(false);为了使滚动流畅 @Kenji 仅用于放置 RecyclerView 的 LinearLayout 内部的第一个视图。或者如果第一次更改没有帮助,则到那个 LinearLayout(RecyclerView 容器)本身。 @DmitryGavrilko 我有一个问题,RecycleView 在 NestedScrollview 中。我不能使用 recycleview.scrollToPosition(X); ,它只是不起作用。在过去的 6 天里,我尝试了所有方法,但我可以克服它。有什么建议吗?非常感谢! 您也可以将android:focusableInTouchMode="false"
设置为recyclerView,这样您就不需要为所有其他视图设置true。
同意 Dmitry Gavrilko,在将 android:focusableInTouchMode="true" 设置为包含 recyclerview 的线性布局后工作。 @Aksiom 将 android:focusableInTouchMode="false" 设置为 recyclerView 对我不起作用。【参考方案2】:
在您的LinearLayout
紧跟在NestedScrollView
之后,按以下方式使用android:descendantFocusability
<LinearLayout
android:layout_
android:layout_
android:orientation="vertical"
android:padding="10dp"
android:descendantFocusability="blocksDescendants">
编辑
由于他们中的许多人对此答案有用,因此也会提供解释。
descendantFocusability
的使用被指定为here。从focusableInTouchMode
到here。
因此,在descendantFocusability
中使用blocksDescendants
不允许它的孩子在触摸时获得焦点,因此可以停止计划外的行为。
对于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()
方法代替setFocusableInTouchMode()
。但是根据这个post,应该避免focusableInTouchMode
,除非在某些情况下,因为它破坏了与Android正常行为的一致性。游戏是一个很好的应用程序示例,它可以很好地利用触控模式中的可聚焦属性。 MapView,如果在谷歌地图中全屏使用,是另一个很好的例子,说明您可以在触摸模式下正确使用可聚焦。
【讨论】:
谢谢!它适用于我的情况。不幸的是 android:focusableInTouchMode="true" 对我不起作用。 这对我有用。我将这行代码添加到我的 recyclerview 的父视图中(在我的例子中,它是一个 LinearLayout),它就像一个魅力。 我使用的是 NestedScrollView,然后是包含 RecyclerView 和 EditText 的 LinearLayout。通过在 LinearLayout 中使用 android:descendantFocusability="blocksDescendants" 修复了滚动行为,但 EditText 无法获得焦点。知道发生了什么吗? @SagarChapagain 是blocksDescendants
的属性来阻止所有后代的焦点。我不确定,但试试beforeDescendants
@JimitPatel 我的问题已使用recyclerView.setFocusable(false); nestedScrollView.requestFocus();
解决了【参考方案3】:
android:descendantFocusability="blocksDescendants"
在 LinearLayout 里面为我工作。
【讨论】:
但这是否意味着关注里面的视图会被屏蔽,用户将无法访问它们?【参考方案4】:我遇到了同样的问题,并通过扩展 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://***.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://***.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 时才使用它。【参考方案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。 Reference
【讨论】:
【参考方案6】:只需在 NestedScrollView 内的 ViewGroup 上添加 android:descendantFocusability="blocksDescendants"
。
【讨论】:
【参考方案7】:我有两个猜测。
首先:试着把这条线放在你的 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_
android:layout_>
<android.support.v4.widget.NestedScrollView
android:layout_
android:layout_
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_
android:layout_
android:orientation="vertical"
android:padding="10dp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:orientation="vertical"
android:padding="16dp">
<TextView
style="@style/TextAppearance.AppCompat.Caption"
android:layout_
android:layout_
android:text="Title:"/>
<TextView
style="@style/TextAppearance.AppCompat.Body1"
android:layout_
android:layout_
android:padding="@dimen/bodyPadding"
android:text="Neque porro quisquam est qui dolorem ipsum"/>
<TextView
style="@style/TextAppearance.AppCompat.Caption"
android:layout_
android:layout_
android:text="Subtitle:"/>
<TextView
style="@style/TextAppearance.AppCompat.Body1"
android:layout_
android:layout_
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_
android:layout_
android:focusable="false"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
我最后一个可能的解决方案。我发誓:)
【讨论】:
并且将 CoordinatorLayout 添加为父视图也不起作用...无论如何谢谢【参考方案8】:此问题是由于回收视图 Focus 造成的。
如果其尺寸扩大了屏幕尺寸,则自动将所有焦点转到回收视图。
将android:focusableInTouchMode="true"
添加到第一个 ChildView 中,例如 TextView
、Button
等(不在 ViewGroup
上,例如 Linear
、Relative
等)对于解决问题很有意义,但 API 级别 25 和上述解决方案不起作用。
只需在您的 ChildView 中添加这两行,如 TextView
、Button
等(不在 ViewGroup
上,如 Linear
、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);
【讨论】:
【参考方案9】:在 Java 代码中,在初始化你的 recyclerView 并设置适配器后,添加以下行:
recyclerView.setNestedScrollingEnabled(false)
您也可以尝试使用 relativeLayout 包装布局,以便视图保持在同一位置,但 recyclerView(滚动)在 xml 层次结构中位于首位。最后一个建议是绝望的尝试:p
【讨论】:
【参考方案10】:请不要使用
android:descendantFocusability="blocksDescendants"
因为 ViewGroup 将阻止其后代接收焦点。这是一个可访问性问题。
如果您想避免nestedscrollview
获得焦点,您可以在nestedscrollview
上方添加一个可聚焦的虚拟布局。
<View
android:layout_
android:layout_
android:focusable="true"
android:focusableInTouchMode="true" />
【讨论】:
【参考方案11】:在我的情况下,它正在滚动并聚焦到 recyclerview,因为我在父线性布局中添加了 android:layout_gravity="center"。 删除后就可以正常使用了。
【讨论】:
【参考方案12】:由于我回复迟了,但可能可以帮助其他人。 只需在您的应用级别 build.gradle 中使用以下或更高版本即可解决问题。
compile com.android.support:recyclerview-v7:23.2.1
【讨论】:
【参考方案13】:要滚动到顶部,只需在 setcontentview
中调用它:
scrollView.SmoothScrollTo(0, 0);
【讨论】:
这不提供问题的答案以上是关于NestedScrollView 内的回收器视图导致滚动从中间开始的主要内容,如果未能解决你的问题,请参考以下文章
无法使用回收视图在 ScrollVIew/NestedScrollView 内滚动 ViewPager
nestedscrollview 内的两个 Recyclerview
LinearLayout 和 NestedScrollView 内的 RecyclerView - 如何滚动到某个位置的项目顶部?