android - make - onBindViewHolder() nunca se llama en la vista en la posición, aunque RecyclerView.findViewHolderForAdapterPosition() devuelve un valor nulo en esa posición
recyclerview dependency (6)
Así que creo que tu pregunta es respondida a continuación por @Pedro Oliveira. El sentido principal de RecycleView es que utiliza algoritmos especiales para el almacenamiento en caché de ViewHolder en cualquier momento. Entonces el siguiente onBindViewHolder (...) puede no funcionar, por ej. Si la vista es estática, o algo más.
Y sobre tu pregunta, piensas usar RecycleView para vistas dinámicas cambiadas. ¡NO LO HAGA! Debido a que RecycleView invalida las vistas y tiene un sistema de almacenamiento en caché, tendrá muchos problemas.
Utilice LinkedListView para esta tarea!
Tengo una lista con 13 artículos (aunque se pueden agregar o eliminar artículos), posiciones 0-12. Cuando el fragmento que contiene el RecyclerView se muestra por primera vez, solo las posiciones 0 a 7 son visibles para el usuario (la posición 7 es solo la mitad visible). En mi adaptador, me Log
cada vez que un titular de vista está enlazado / enlazado (idk si la gramática se aplica aquí) y registra su posición.
Adaptador
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Log.d(TAG, "onBindViewHolder() position: " + position);
...
}
Desde mi Log
veo que las posiciones 0-7 están vinculadas:
Tengo un método selectAll()
que obtiene cada ViewHolder
por posición de adaptador. Si el holder
devuelto NO es null
, uso el holder
devuelto para actualizar la vista para mostrar que está seleccionado. Si el titular devuelto es null
, llamo a selectOnBind()
un método que marca la vista en esa actualización de posición para mostrar que está seleccionado cuando está enlazado en lugar de en tiempo real, ya que no se muestra actualmente:
public void selectAll() {
for (int i = 0; i < numberOfItemsInList; i++) {
MyAdapter.ViewHolder holder = (MyAdapter.ViewHolder)
mRecyclerView.findViewHolderForAdapterPosition(i);
Log.d(TAG, "holder at position " + i + " is " + holder);
if (holder != null) {
select(holder);
} else {
selectOnBind(i);
}
}
}
En este método Log
el holder
junto con su posición:
Así que hasta este punto todo parece normal. Tenemos las posiciones 0-7 que muestran, y de acuerdo con el Log
estas son las posiciones vinculadas. Cuando selectAll()
sin cambiar las vistas visibles (desplazamiento) veo que las posiciones 0-7 están definidas y 8-12 son null
. Hasta ahora tan bueno.
Aquí es donde se pone interesante. Si después de llamar a selectAll()
me selectAll()
hacia abajo en la lista, las posiciones 8 y 9 no muestran que están seleccionadas.
Al revisar el Log
, veo que es porque nunca están vinculados, aunque se informó que eran null
:
Aún más confuso es que esto no sucede todas las veces. Si ejecuto la aplicación por primera vez y pruebo esto, puede funcionar. Pero parece suceder sin falta después. Supongo que tiene algo que ver con el reciclado de las vistas, pero aun así, ¿no tendrían que ser limitadas?
EDITAR (6-29-16)
Después de una actualización de AndroidStudio parece que no puedo reproducir el error. Funciona como esperaba, vinculando las vistas nulas. Si este problema vuelve a aparecer, volveré a esta publicación.
Creo que jugar con vista no es una buena idea en Recyclerview. El enfoque que siempre utilizo para seguir es simplemente para introducir una bandera en el modelo que utiliza RecyclerView. Supongamos que tu modelo es como ...
class MyModel{
String name;
int age;
}
Si está rastreando si la vista está seleccionada o no, introduzca un modelo booleano. Ahora se verá como
class MyModel{
String name;
int age;
boolean isSelected;
}
Ahora su casilla de verificación se seleccionará / deseleccionará en función de la nueva marca isSelected (en onBindViewHolder ()). En cada selección en la vista cambiará el valor del valor seleccionado del modelo correspondiente a verdadero, y en la no seleccionada, cambiarlo a falso. En su caso, simplemente ejecute un ciclo para cambiar el valor isSelected de todos los modelos a verdadero y luego llame a notifyDataSetChanged()
.
Por ejemplo, supongamos que su lista es
ArrayList<MyModel> recyclerList;
private void selectAll(){
for(MyModel myModel:recyclerList)
myModel.isSelected = true;
notifyDataSetChanged();
}
Mi sugerencia, mientras utiliza recyclerView o ListView para tratar de jugar con menos vistas.
Así que en tu caso ...
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.clickableView.setTag(position);
holder.selectableView.setTag(position);
holder.checkedView.setChecked(recyclerList.get(position).isSelected);
Log.d(TAG, "onBindViewHolder() position: " + position);
...
}
@Override
public void onClick(View view){
int position = (int)view.getTag();
recyclerList.get(position).isSelected = !recyclerList.get(position).isSelected;
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int position = (int)buttonView.getTag();
recyclerList.get(position).isSelected = isChecked;
}
Espero que te ayude. Por favor, avísame si necesitas más explicaciones :)
Cuando tenga un gran número de elementos en la lista que haya pasado al recyclerview adapter
, no encontrará el problema de que onBindViewHolder()
no se ejecuta mientras se desplaza.
Pero si la lista tiene menos elementos (he comprobado el tamaño de la lista 5), puede encontrar este problema.
Mejor solución es comprobar el list size
.
Por favor encuentre el código de muestra a continuación.
private void setupAdapter(){
if (list.size() <= 10){
recycler.setItemViewCacheSize(0);
}
recycler.setAdapter(adapter);
recycler.setLayoutManager(linearLayoutManager);
}
En su caso, las vistas para las posiciones 8 y 9 no se están reciclando, se están separando de la ventana y se adjuntarán nuevamente. Y para esta vista onBindViewHolder
no se llama onViewAttachedToWindow
, solo se llama onViewAttachedToWindow
. Si anula estas funciones en su adaptador, puede ver de qué estoy hablando.
@Override
public void onViewRecycled(ViewHolder vh){
Log.wtf(TAG,"onViewRecycled "+vh);
}
@Override
public void onViewDetachedFromWindow(ViewHolder viewHolder){
Log.wtf(TAG,"onViewDetachedFromWindow "+viewHolder);
}
Ahora, para resolver su problema, debe hacer un seguimiento de las vistas que se suponía que debían reciclarse, pero luego desapegarse y luego realizar el proceso de la sección en
@Override
public void onViewAttachedToWindow(ViewHolder viewHolder){
Log.wtf(TAG,"onViewAttachedToWindow "+viewHolder);
}
Esto está sucediendo porque:
- Las vistas no se agregan a recyclerview (
getChildAt
no funcionará y devolverá nulo para esa posición) - También se almacenan en caché (no se llamará
onBind
)
Llamar a recyclerView.setItemViewCacheSize(0)
solucionará este "problema".
Debido a que el valor predeterminado es 2 ( private static final int DEFAULT_CACHE_SIZE = 2;
en RecyclerView.Recycler
), siempre obtendrá 2 vistas que no llamarán a onBind
pero que no se agregarán al reciclador.
Las respuestas de Pedro Oliveira y Zartha son excelentes para entender el problema, pero no veo ninguna solución con la que esté contento.
Creo que tienes 2 buenas opciones dependiendo de lo que estés haciendo:
Opción 1
Si desea que onBindViewHolder()
sea llamado para una vista fuera de pantalla independientemente de si está en caché / separado o no, entonces puede hacerlo:
RecyclerView.ViewHolder view_holder = recycler_view.findViewHolderForAdapterPosition( some_position );
if ( view_holder != null )
{
//manipulate the attached view
}
else //view is either non-existant or detached waiting to be reattached
notifyItemChanged( some_position );
La idea es que si la vista se almacena en caché / se separa, entonces notifyItemChanged()
al adaptador que notifyItemChanged()
que la vista no es válida, lo que hará que se onBindViewHolder()
a onBindViewHolder()
.
opcion 2
Si solo desea ejecutar un cambio parcial (y no todo dentro de onBindViewHolder()
), luego dentro de onBindViewHolder( ViewHolder view_holder, int position )
, debe almacenar la position
en view_holder
y ejecutar el cambio que desee en onViewAttachedToWindow( ViewHolder view_holder )
.
Recomiendo la opción 1 por simplicidad a menos que su onBindViewHolder()
esté haciendo algo intensivo como jugar con Bitmaps.