android - scrolling - scroll recyclerview
La vista del reciclador dentro de NestedScrollView hace que el desplazamiento comience en el medio (10)
Estoy obteniendo un comportamiento de desplazamiento extraño cuando agrego un RecyclerView dentro de un NestedScrollView.
Lo que sucede es que cada vez que la vista de desplazamiento tiene más filas de las que se pueden mostrar en la pantalla, tan pronto como se inicia la actividad, NestedScrollView comienza con un desplazamiento desde la parte superior (imagen 1). Si hay pocos elementos en la vista de desplazamiento para que puedan mostrarse todos a la vez, esto no sucede (imagen 2).
Estoy usando la versión 23.2.0 de la biblioteca de soporte.
Imagen 1 : INCORRECTO: comienza con desplazamiento desde la parte superior
Imagen 2 : CORRECTO: pocos elementos en la vista de reciclador
Estoy pegando debajo de mi código de diseño:
<?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>
¿Me estoy perdiendo de algo? ¿Alguien tiene alguna idea de cómo solucionar este problema?
Actualización 1
Funciona correctamente si coloco el siguiente código al inicializar mi Actividad:
sv.post(new Runnable() {
@Override
public void run() {
sv.scrollTo(0,0);
}
});
Donde sv es una referencia a NestedScrollView, sin embargo, parece un gran truco.
Actualización 2
Según lo solicitado, aquí está mi código de adaptador:
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());
}
}
Y aquí está mi 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);
}
}
Y aquí está el diseño de cada elemento en RecyclerView (es solo
android.R.layout.simple_spinner_item
; esta pantalla es solo para mostrar un ejemplo de este error):
<?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"/>
Como llego tarde en responder, pero puede ayudar a alguien más. Simplemente use la versión siguiente o superior en el nivel de aplicación build.gradle y se eliminará el problema.
compile com.android.support:recyclerview-v7:23.2.1
En el código Java, después de inicializar su recyclerView y configurar el adaptador, agregue esta línea:
recyclerView.setNestedScrollingEnabled(false)
También puede intentar ajustar el diseño con un diseño relativo para que las vistas permanezcan en la misma posición, pero recyclerView (que se desplaza) es el primero en la jerarquía xml. La última sugerencia es un intento desesperado: p
En mi caso, este código resuelve el problema mío
RecyclerView recyclerView = findViewById(R.id.recyclerView);
NestedScrollView nestedScrollView= findViewById(R.id.nestedScrollView);
recyclerView.setFocusable(false);
nestedScrollView.requestFocus();
//populate recyclerview here
Mi diseño contiene un diseño principal como NestedScrollView que tiene un diseño lineal LinearLayout. LinearLayout tiene orientación "vertical" y ChildRecyclerView y EditText. Reference
En su
LinearLayout
inmediatamente después de
NestedScrollView
, use
android:descendantFocusability
de la siguiente manera
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp"
android:descendantFocusability="blocksDescendants">
EDITAR
Dado que muchos de ellos obtienen esta respuesta útil, también proporcionarán una explicación.
El uso de
descendantFocusability
se da
here
.
Y a partir de
focusableInTouchMode
por
here
.
Por lo tanto, usar
blocksDescendants
en
descendantFocusability
no permite que su hijo se enfoque mientras toca y, por lo tanto, se puede detener el comportamiento no planificado.
En cuanto a
focusInTouchMode
,
AbsListView
y
RecyclerView
llaman al método
setFocusableInTouchMode(true);
en su constructor por defecto, por lo que no es necesario usar ese atributo en sus diseños XML.
Y para
NestedScrollView
se usa el siguiente método:
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();
}
Aquí, se usa el método
setFocusable()
lugar de
setFocusableInTouchMode()
.
Pero de acuerdo con esta
post
, se debe evitar focusableInTouchMode a menos que para ciertas condiciones, ya que rompe la coherencia con el comportamiento normal de Android.
Un juego es un buen ejemplo de una aplicación que puede hacer un buen uso de la propiedad enfocable en modo táctil.
MapView, si se usa en pantalla completa como en Google Maps, es otro buen ejemplo de dónde puede usar correctamente el enfoque en modo táctil.
Resolví tal problema estableciendo:
<ImageView ...
android:focusableInTouchMode="true"/>
a mi vista sobre RecyclerView (que estaba oculto después de un desplazamiento no deseado). Intente establecer esta propiedad en su LinearLayout sobre RecyclerView o en LinearLayout, que es el contenedor de RecyclerView (me ayudó en otro caso).
Como veo en la fuente NestedScrollView, trata de enfocar al primer hijo posible en onRequestFocusInDescendants y si solo RecyclerView es enfocable, gana.
Editar (gracias a Waran): y para un desplazamiento suave, no olvide configurar
yourRecyclerView.setNestedScrollingEnabled(false);
Tengo dos conjeturas.
Primero: intente poner esta línea en su NestedScrollView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
Segundo: uso
<android.support.design.widget.CoordinatorLayout
como la vista de tus padres Me gusta esto
<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>
Mi última solución posible. Lo juro :)
Tuve el mismo problema y me molesté al extender NestedScrollView y deshabilitar el enfoque de los niños. Por alguna razón, RecyclerView siempre solicitó enfoque incluso cuando abrí y cerré el cajón.
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;
}
}
para desplazarse a la parte superior, solo llame a esto en
setcontentview
:
scrollView.SmoothScrollTo(0, 0);
Este problema llega debido a la vista de reciclaje Focus.
Automáticamente, todo el foco se ha ido a la vista de reciclaje si su tamaño extendía el tamaño de la pantalla.
agregar
android:focusableInTouchMode="true"
al primer ChildView como
TextView
,
Button
, etc. (No en
ViewGroup
como
Linear
,
Relative
,
ViewGroup
) tiene sentido para resolver el problema, pero la solución API Level 25 y superior no funciona.
Simplemente agregue estas 2 líneas en su ChildView como
TextView
,
Button
y así
ViewGroup
(no en
ViewGroup
como
Linear
,
Relative
,
ViewGroup
)
android:focusableInTouchMode="true"
android:focusable="true"
Acabo de enfrentar este problema en el nivel 25 de API. Espero que otras personas no pierdan el tiempo en esto.
Para un desplazamiento suave en RecycleView, agregue esta línea
android:nestedScrollingEnabled="false"
pero agregar estos atributos solo funciona con el nivel de API 21 o superior. Si desea que el desplazamiento de suavizado funcione por debajo del nivel 25 de API, agregue esta línea en su clase
mList = findViewById(R.id.recycle_list);
ViewCompat.setNestedScrollingEnabled(mList, false);
android:descendantFocusability="blocksDescendants"
inside LinearLayout Funcionó para mí.