Pregunta ViewPager PagerAdapter no actualiza la vista


Estoy usando ViewPager de la biblioteca de compatibilidad. He logrado mostrar varias vistas a través de las cuales puedo pasar la página.

Sin embargo, estoy teniendo dificultades para descubrir cómo actualizar ViewPager con un nuevo conjunto de Vistas.

He intentado todo tipo de cosas como llamar mAdapter.notifyDataSetChanged(), mViewPager.invalidate() incluso creando un nuevo adaptador cada vez que quiero usar una nueva lista de datos.

Nada ha ayudado, las textviews permanecen sin cambios desde los datos originales.

Actualizar:  Hice un pequeño proyecto de prueba y casi he podido actualizar las vistas. Pegaré la clase a continuación.

Sin embargo, lo que no parece actualizarse es la segunda vista, la 'B' permanece, debe mostrar 'Y' después de presionar el botón de actualización.

public class ViewPagerBugActivity extends Activity {

    private ViewPager myViewPager;
    private List<String> data;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        data = new ArrayList<String>();
        data.add("A");
        data.add("B");
        data.add("C");

        myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
        myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

        Button updateButton = (Button) findViewById(R.id.update_button);
        updateButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                updateViewPager();
            }
        });
    }

    private void updateViewPager() {
        data.clear();
        data.add("X");
        data.add("Y");
        data.add("Z");
        myViewPager.getAdapter().notifyDataSetChanged();
    }

    private class MyViewPagerAdapter extends PagerAdapter {

        private List<String> data;
        private Context ctx;

        public MyViewPagerAdapter(Context ctx, List<String> data) {
            this.ctx = ctx;
            this.data = data;
        }

        @Override
        public int getCount() {
            return data.size();
        }

        @Override
        public Object instantiateItem(View collection, int position) {
            TextView view = new TextView(ctx);
            view.setText(data.get(position));
            ((ViewPager)collection).addView(view);
            return view;
        }

        @Override
        public void destroyItem(View collection, int position, Object view) {
             ((ViewPager) collection).removeView((View) view);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Parcelable saveState() {
            return null;
        }

        @Override
        public void restoreState(Parcelable arg0, ClassLoader arg1) {
        }

        @Override
        public void startUpdate(View arg0) {
        }

        @Override
        public void finishUpdate(View arg0) {
        }
    }
}

516
2017-08-31 20:54


origen


Respuestas:


Hay varias formas de lograr esto.

La primera opción es más fácil, pero un poco más ineficiente.

Anular getItemPosition en tus PagerAdapter Me gusta esto:

public int getItemPosition(Object object) {
    return POSITION_NONE;
}

De esta manera, cuando llamas notifyDataSetChanged(), el buscador de vistas eliminará todas las vistas y las volverá a cargar todas. Como tal, se obtiene el efecto de recarga.

La segunda opción, sugerido por Alvaro Luis Bustamante (anteriormente alvarolb), Es para setTag() método en instantiateItem() al crear una nueva vista. Entonces, en lugar de usar notifyDataSetChanged(), puedes usar findViewWithTag() para encontrar la vista que quieres actualizar

El segundo enfoque es muy flexible y de alto rendimiento. Felicitaciones a alvarolb por la investigación original.


746
2017-09-02 17:28



No creo que haya ningún tipo de error en el PagerAdapter. El problema es que entender cómo funciona es un poco complejo. Mirando las soluciones explicadas aquí, hay un malentendido y, por lo tanto, un uso deficiente de las vistas creadas desde mi punto de vista.

Los últimos días he estado trabajando con PagerAdapter y ViewPagery encontré lo siguiente:

los notifyDataSetChanged() método en el PagerAdapter solo notificará al ViewPager que las páginas subyacentes han cambiado. Por ejemplo, si ha creado / eliminado páginas dinámicamente (agregando o eliminando elementos de su lista), ViewPager debería encargarse de eso. En este caso, creo que el ViewPager determina si una nueva vista debe ser eliminada o instanciada usando el getItemPosition() y getCount() métodos.

Creo que ViewPager, después de notifyDataSetChanged() llamada toma sus vistas de niño y comprueba su posición con el getItemPosition(). Si para una vista hija este método regresa POSITION_NONE, el ViewPager entiende que la vista ha sido eliminada, llamando al destroyItem()y eliminando esta vista

De esta manera, anulando getItemPosition() para siempre regresar POSITION_NONE es completamente incorrecto si solo quiere actualizar el contenido de las páginas, porque las vistas creadas previamente se destruirán y se crearán nuevas cada vez que llame notifyDatasetChanged(). Puede parecer que no está tan mal solo por unos pocos TextViews, pero cuando tiene vistas complejas, como ListViews pobladas de una base de datos, esto puede ser un problema real y una pérdida de recursos.

Por lo tanto, hay varios enfoques para cambiar de forma eficiente el contenido de una vista sin tener que eliminar y crear una instancia de la vista de nuevo. Depende del problema que quieras resolver. Mi enfoque es usar el setTag() método para cualquier vista instanciada en el instantiateItem() método. Entonces, cuando desee cambiar los datos o invalidar la vista que necesita, puede llamar al findViewWithTag() método en el ViewPager para recuperar la vista previamente instanciada y modificarla / usarla como quiera sin tener que eliminar / crear una nueva vista cada vez que quiera actualizar algún valor. 

Imagine, por ejemplo, que tiene 100 páginas con 100 TextViews y solo desea actualizar un valor periódicamente. Con los enfoques explicados anteriormente, esto significa que está eliminando e instanciando 100 TextViews en cada actualización. No tiene sentido...


445
2017-11-06 01:11



Cambiar el FragmentPagerAdapter a FragmentStatePagerAdapter.

Anular getItemPosition() método y devolución POSITION_NONE.

Eventualmente, escuchará el notifyDataSetChanged() en el buscapersonas


69
2017-09-05 02:07



La respuesta dado por alvarolb es definitivamente la mejor manera de hacerlo. Basándose en su respuesta, una forma fácil de implementar esto es simplemente almacenar las vistas activas por posición:

SparseArray<View> views = new SparseArray<View>();

@Override
public Object instantiateItem(View container, int position) {
    View root = <build your view here>;
    ((ViewPager) container).addView(root);
    views.put(position, root);
    return root;
}

@Override
public void destroyItem(View collection, int position, Object o) {
    View view = (View)o;
    ((ViewPager) collection).removeView(view);
    views.remove(position);
    view = null;
}

Luego, una vez anulando el notifyDataSetChanged método puede actualizar las vistas ...

@Override
public void notifyDataSetChanged() {
    int key = 0;
    for(int i = 0; i < views.size(); i++) {
       key = views.keyAt(i);
       View view = views.get(key);
       <refresh view with new data>
    }
    super.notifyDataSetChanged();
}

En realidad puedes usar un código similar en instantiateItem y notifyDataSetChanged para refrescar tu vista En mi código uso exactamente el mismo método.


34
2018-06-06 01:55



Tenía el mismo problema. Para mí, funcionó para extender FragmentStatePagerAdapter y anular los siguientes métodos:

@Override
public Parcelable saveState() {
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {

}

21
2018-06-17 07:57



Después de horas de frustración al intentar todas las soluciones anteriores para superar este problema y también probar muchas soluciones en otras preguntas similares como esta, esta y esta que todos FALLARON conmigo para resolver este problema y para hacer que ViewPager para destruir el viejo Fragment y llene el pager con el nuevo Fragments. He resuelto el problema de la siguiente manera:

1) Hacer el ViewPager clase para extender FragmentPagerAdapter como sigue:

 public class myPagerAdapter extends FragmentPagerAdapter {

2) Crea un artículo para el ViewPager que almacena el title y el fragment como sigue:

public class PagerItem {
private String mTitle;
private Fragment mFragment;


public PagerItem(String mTitle, Fragment mFragment) {
    this.mTitle = mTitle;
    this.mFragment = mFragment;
}
public String getTitle() {
    return mTitle;
}
public Fragment getFragment() {
    return mFragment;
}
public void setTitle(String mTitle) {
    this.mTitle = mTitle;
}

public void setFragment(Fragment mFragment) {
    this.mFragment = mFragment;
}

}

3) Hacer el constructor de ViewPager toma mi FragmentManager instancia para almacenarlo en mi class como sigue:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mPagerItems = pagerItems;
}

4) Crea un método para restablecer el adapter datos con los nuevos datos al eliminar todo el anterior fragment desde el fragmentManager directamente a hacer el adapter para establecer el nuevo fragment de la nueva lista nuevamente como sigue:

public void setPagerItems(ArrayList<PagerItem> pagerItems) {
    if (mPagerItems != null)
        for (int i = 0; i < mPagerItems.size(); i++) {
            mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
        }
    mPagerItems = pagerItems;
}

5) Del contenedor Activity o Fragment no vuelva a inicializar el adaptador con los datos nuevos. Establecer los nuevos datos a través del método setPagerItems con los nuevos datos de la siguiente manera:

ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));

mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();

Espero que ayude.


19
2018-03-08 18:01



Dos años y medio después de que el OP hiciera su pregunta, este problema sigue siendo, bueno, todavía un problema. Es obvio que la prioridad de Google en esto no es particularmente alta, así que en lugar de encontrar una solución, encontré una solución. El gran avance para mí fue descubrir cuál era la verdadera causa del problema (ver la respuesta aceptada en esta publicación ) Una vez que era evidente que el problema era que las páginas activas no se actualizan adecuadamente, mi solución fue obvia:

En mi Fragmento (las páginas):

  • Tomé todo el código que rellena el formulario de onCreateView y lo puse en una función llamada PopulateForm que se puede llamar desde cualquier lugar, en lugar de desde el marco. Esta función intenta obtener la Vista actual usando getView, y si eso es nulo, simplemente regresa. Es importante que PopulateForm contenga solo el código que se muestra; el resto del código que crea los detectores de FocusChange y similares todavía está en OnCreate.
  • Cree un booleano que se pueda usar como un indicador que indique que el formulario se debe volver a cargar. El mío es mbReloadForm
  • Reemplazar OnResume () para llamar a PopulateForm () si se establece mbReloadForm.

En mi Actividad, donde hago la carga de las páginas:

  • Vaya a la página 0 antes de cambiar cualquier cosa. Estoy usando FragmentStatePagerAdapter, así que sé que dos o tres páginas se ven afectadas como máximo. Cambiar a la página 0 asegura que solo tenga el problema en las páginas 0, 1 y 2.
  • Antes de borrar la lista anterior, tome su tamaño (). De esta forma sabrá cuántas páginas se ven afectadas por el error. Si> 3, reduzca a 3 - si usa un PagerAdapter diferente, tendrá que ver cuántas páginas tiene que tratar (¿quizás todas?)
  • Recargue los datos y llame a pageAdapter.notifyDataSetChanged ()
  • Ahora, para cada una de las páginas afectadas, vea si la página está activa usando pager.getChildAt (i): esto le indica si tiene una vista. Si es así, llame a pager.PopulateView (). Si no, configure el indicador ReloadForm.

Después de esto, cuando vuelvas a cargar un segundo conjunto de páginas, el error seguirá causando que algunos muestren los datos antiguos. Sin embargo, ahora se actualizarán y verá los nuevos datos: sus usuarios no sabrán que la página fue incorrecta alguna vez porque esta actualización ocurrirá antes de que vean la página.

¡Espero que esto ayude a alguien!


4
2018-02-07 11:08



Una manera mucho más fácil: use un FragmentPagerAdaptery envuelve tus vistas paginado en fragmentos. Ellos se actualizan


4
2017-11-14 22:08



En caso de que alguien esté usando FragmentStatePagerAdapter Adaptador basado (que permitirá a ViewPager crear las páginas mínimas necesarias para la visualización, como máximo 2 para mi caso), la respuesta de @ rui.araujo de sobreescribir getItemPosition en su adaptador no causará desperdicio significativo, pero aún puede mejorarse.

En pseudo código:

public int getItemPosition(Object object) {
    YourFragment f = (YourFragment) object;
    YourData d = f.data;
    logger.info("validate item position on page index: " + d.pageNo);

    int dataObjIdx = this.dataPages.indexOf(d);

    if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
        logger.info("data changed, discard this fragment.");
        return POSITION_NONE;
    }

    return POSITION_UNCHANGED;
}

3
2017-07-11 05:56