NestedScrollView内的Recycler视图使滚动在中间开始

当我在NestedScrollView中添加一个RecyclerView时,我得到了一个奇怪的滚动行为。

会发生什么情况是,只要滚动视图的行数比屏幕上显示的行数多,一旦活动启动,NestedScrollView就会从顶部(图1)开始偏移。 如果滚动视图中的项目很less,以便它们都可以一次显示,则不会发生(图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的引用,但是看起来相当黑客。

更新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 – 这个屏幕只是用来显示这个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"/> 

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

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

我的观点上面RecyclerView(这是不必要的滚动后隐藏)。 尝试将此属性设置为RecyclerView上方的LinearLayout或RecyclerView的容器LinearLayout(在另一个案例中帮助我)。

正如我在NestedScrollView源中看到的,它试图将第一个可能的子集中在onRequestFocusInDescendants中,并且如果只有RecyclerView是可以聚焦的,它就会胜出。

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

NestedScrollView之后的LinearLayout中,按以下方式使用android:descendantFocusability

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

编辑

由于他们很多得到这个答案有用,也将提供解释。

这里给出了使用descendantFocusability可聚焦性。 而至于focusableInTouchMode在这里 。 所以在blocksDescendants中使用blocksDescendants不会让孩子在触摸时获得焦点,因此可以停止计划外的行为。

至于focusInTouchModeAbsListViewRecyclerView调用方法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() 。 但根据这篇文章 , focusableInTouchMode应该避免,除非在某些情况下,因为它违反Android正常行为的一致性。 游戏是一个很好的例子,可以很好地利用可触摸模式属性中的焦点。 如果在Google地图中以全屏模式使用MapView,那么MapView是另一个很好的例子,您可以在触摸模式下正确使用焦点。

我遇到了同样的问题,并通过扩展NestedScrollView和禁用聚焦子项来解决问题。 出于某种原因,即使在我刚刚打开和closures抽屉时,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; } } 

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

 recyclerView.setNestedScrollingEnabled(false) 

你也可以试着用一个relativeLayout来包装布局,以便视图保持在同一个位置,但是recyclerView(哪个滚动)在xml层次结构中是第一个。 最后一个build议是绝望的尝试:p

我有两个猜测。

首先:尝试把这一行放在你的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> 

我最后的可能解决scheme 我发誓 :)

 android:descendantFocusability="blocksDescendants" 

在LinearLayout里面为我工作。

在我的情况下,这个代码解决了我的问题

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

由于我的反应迟,但可能可以帮助别人。 只需在应用程序级别的build.gradle中使用以下或更高版本,问题就会被删除。

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

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

 scrollView.SmoothScrollTo(0, 0);