NestedScrollView 中的回收视图使滚动从中间开始

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

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

我使用的是支持库的23.2.0版本。

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

Image 1

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

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

这是我的观点持有者:

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

下面是回收视图中每个项目的布局(它只是 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"/>
73350 次浏览

我有两个猜测。

首先: 尝试在 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>

这是我最后的办法了,我发誓

在 Java 代码中,在初始化回收视图并设置适配器之后,添加以下代码行:

recyclerView.setNestedScrollingEnabled(false)

您还可以尝试使用 relativeLayout 包装布局,以便视图保持在相同的位置,但是在 xml 层次结构中,回收视图(滚动)位于第一位。最后一个建议是孤注一掷的尝试

我也遇到过同样的问题,通过扩展 NestedScrollView 和禁用儿童聚焦功能解决了这个问题。出于某种原因,回收视图总是要求焦点,即使我只是打开和关闭抽屉。

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

我通过设定以下条件解决了这个问题:

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

在我的回收视图(在不需要的滚动之后隐藏)上面的视图。尝试将此属性设置为您的回收视图上方的 LinearLayout,或者设置为回收视图容器的 LinearLayout (在另一种情况下帮助了我)。

正如我在 NestedScrollView 源代码中看到的那样,它试图将第一个可能的子级聚焦在 onRequestFocusInDescendes 中,如果仅仅回收视图是可聚焦的,那么它就赢了。

编辑(感谢 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的使用给出了 给你。 因此,在 descendantFocusability中使用 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 正常行为的一致性。游戏是一个很好的应用程序的例子,它可以很好地利用触摸模式下的可对焦属性。MapView,如果像谷歌地图一样在全屏中使用,是另一个很好的例子,说明在触摸模式下可以正确使用聚焦。

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

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

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

scrollView.SmoothScrollTo(0, 0);
android:descendantFocusability="blocksDescendants"

内部线性布局为我工作。

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

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


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


//populate recyclerview here

我的布局包含一个作为 NestedScrollView 的父布局,它有一个子 LinearLayout。LinearLayout 的方向是“垂直的”,并且它的子元素循环视图和 EditText。参考文献

这个问题是由于回收视图 Focus 造成的。

如果视图的大小扩展了屏幕的大小,则自动将所有焦点转移到回收视图。

android:focusableInTouchMode="true"添加到像 TextViewButton等等的第一个儿童视图(而不是像 LinearRelative等等在 ViewGroup上)对于解决问题是有意义的,但是 API 25级及以上的解决方案不起作用。

只要在你的儿童视图中添加这两行,如 TextViewButton等(不要在 ViewGroup上,如 LinearRelative等)

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

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

在回收视图中添加这一行 以实现平滑滚动

 android:nestedScrollingEnabled="false"

但是添加这些属性只适用于 API 级别21或更高的情况。如果你想在 API 级别25以下平滑滚动工作,那么在你的类中添加这一行

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

只需在 NestedScrollView 内的 ViewGroup 上添加 android:descendantFocusability="blocksDescendants"

请不要使用

android:descendantFocusability="blocksDescendants"

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

如果您想避免 nestedscrollview接收焦点,您可以添加一个虚拟布局,这是高于 nestedscrollview可调焦。

   <View
android:layout_width="0dp"
android:layout_height="0dp"
android:focusable="true"
android:focusableInTouchMode="true" />

在我的情况下,它是滚动和聚焦到回收视图,由于,我已经添加了母线性布局的 机器人: 布局 _ 重力 = “中心”。 除去它之后就可以正常工作了。