ViewPager PagerAdapter no actualiza la vista

597

Estoy usando ViewPager de la biblioteca de compatibilidad. Lo he conseguido mostrando varias vistas que puedo ver.

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 crear un nuevo adaptador cada vez que quiero usar una nueva Lista de datos.

Nada ha ayudado, las vistas de texto permanecen sin cambios con respecto a los datos originales.

Actualización: 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, debería 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) {
        }
    }
}
C0deAttack
fuente
1
Consulte stackoverflow.com/questions/12510404/… para ver un posible error con el FragmentStatePagerAdapter.
samis
2
Lamento dragar esto. Tu pregunta realmente me ayudó, ¡así que gracias! Lo único que no entiendo es cómo cambia la referencia a los datos en el Adaptador Pager cuando se actualiza en la Actividad
Ewanw
Haga que su adaptador extienda BaseAdapter Consulte esto: stackoverflow.com/questions/13664155/…
1
Aparentemente, si vuelve a configurar el adaptador en su buscapersonas, se restablece.
EpicPandaForce
Tengo un problema en el buscapersonas stackoverflow.com/questions/41217237/…
chris

Respuestas:

856

Hay varias formas de lograr esto.

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

Anular getItemPositionen tu PagerAdaptercomo este:

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

De esta forma, cuando llame notifyDataSetChanged(), el localizador de vistas eliminará todas las vistas y las volverá a cargar todas. Así se obtiene el efecto de recarga.

La segunda opción, sugerida por Álvaro Luis Bustamante (anteriormente alvarolb) , es utilizar elsetTag() método para instantiateItem()crear una nueva vista. Luego, en lugar de usar notifyDataSetChanged(), puede usar findViewWithTag()para encontrar la vista que desea actualizar.

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

rui.araujo
fuente
44
Mejor que mi trabajo, parece no tener ningún efecto secundario, gracias.
C0deAttack
3
Tengo un problema similar. Mi viewPager mostrará los nuevos resultados de búsqueda pero no volverá a la primera entrada del cursor por defecto. ¿Alguien sabe cómo restablecer la posición al actualizar el conjunto de datos?
mAndroid
1
@mAndroid, debe presentar una pregunta por separado con más detalles.
rui.araujo
1
regresar POSITION_NONE aparentemente agrega mucha ineficiencia a la aplicación general ya que esto hace que Android elimine la vista asociada con la posición consultada. La vista debe volver a crearse antes de mostrarse. Resuelve el problema de que las vistas no se actualicen, pero probablemente no sea la mejor solución para diseños complejos.
wufoo
11
Mejor respuesta (sin eliminar todos los elementos) -> stackoverflow.com/a/10852046/898056
yhpark
484

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 mal uso de las vistas instanciadas desde mi punto de vista.

Los últimos días he estado trabajando con PagerAdaptery ViewPager, y encontré lo siguiente:

El notifyDataSetChanged()método en el PagerAdaptersolo notificará ViewPagerque las páginas subyacentes han cambiado. Por ejemplo, si ha creado / eliminado páginas dinámicamente (agregando o eliminando elementos de su lista), ViewPagerdebe ocuparse de eso. En este caso, creo que ViewPagerdetermina si una nueva vista debe eliminarse o instanciarse utilizando los métodos getItemPosition()y getCount().

Creo que ViewPager, después de que una notifyDataSetChanged()llamada toma sus vistas secundarias y comprueba su posición con el getItemPosition(). Si para una vista secundaria este método regresa POSITION_NONE, ViewPagercomprende que la vista ha sido eliminada, invocando destroyItem()y eliminando esta vista.

De esta manera, anular el getItemPosition()retorno siempre POSITION_NONEes completamente incorrecto si solo desea 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 TextViewsegundos, 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 manera eficiente el contenido de una vista sin tener que eliminar e instanciar la vista nuevamente. 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 ViewPagerpara recuperar la vista previamente instanciada y modificarla / usarla como desee sin tener que eliminar / crear una nueva vista cada vez que desee para actualizar algún valor.

Imagine, por ejemplo, que tiene 100 páginas con 100 TextViewsy 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...

Alvaro Luis Bustamante
fuente
11
+1: su solución no solo funciona de maravilla, encontré exactamente el problema que describió aquí cuando tuve que actualizar páginas que contenían pestañas con vistas de lista, vistas de texto e imágenes. (OutOfErrorExceptions y tal ...) ¡Gracias!
schlingel
3
@alvarolb: Hola, realmente no sé a qué te refieres con el setTagmétodo onInstantiateItem. ¿Quieres actualizar esta respuesta con algo de codificación? Gracias.
Huy Tower
35
Sería perfecto si escribieras un ejemplo.
Sami Eltamawy
13
Como alguien dice: ¡un ejemplo sería apreciado!
Jey10
15
Hace 6 años, nadie puede dar un ejemplo.
Oussaki
80

Cambiar el FragmentPagerAdaptera FragmentStatePagerAdapter.

getItemPosition()Método de anulación y devolución POSITION_NONE.

Eventualmente, escuchará el notifyDataSetChanged()localizador en vista.

Equipo
fuente
Creo que se supone que debes implementar toda la getItemPosition correctamente Y devolver POSITION_NONE si no se encuentra el fragmento.
tkhduracell
46

La respuesta dada por alvarolb es definitivamente la mejor manera de hacerlo. Sobre la base de su respuesta, una manera 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 notifyDataSetChangedmé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, puede usar un código similar en instantiateItemy notifyDataSetChangedpara actualizar su vista. En mi código, uso exactamente el mismo método.

Grimmace
fuente
8
¿Puedes proporcionar un ejemplo de adaptador completo? ¿Se está haciendo esto usando Fragmentos o no?
99
<actualizar vista con nuevos datos> ?? ¿Cuál es el significado de esto o tenemos que agregar el punto de vista aquí o nada?
Akarsh M
Este método actualiza todas las vistas. Si solo desea actualizar una vista, puede escribir su propio método notifyDataItemChanged como este: public void notifyDataItemChanged (int pos) {TextView tv = (TextView) views.get (pos) .findViewById (R.id.tv); tv.setText (list.get (pos)); super.notifyDataSetChanged (); }
Kai Wang
Este código falla si el nuevo tamaño del localizador es menor que el tamaño anterior
Anton Duzenko
@AntonDuzenko correcto, eliminar vistas es bastante difícil.
MLProgrammer-CiM
28

Tuve 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) {

}
Mario Mosca
fuente
Esta es la única solución que me funcionó también. Mi caso no es actualizar el fragmento, sino eliminar o agregar fragmentos dinámicamente. Sin extender FragmentStatePagerAdapter, ViewPager devuelve fragmentos almacenados en caché, que es una posición incorrecta. ¡Gracias!
Sira Lam el
Funciona bien en la biblioteca de soporte 28.0.0
Oleksii K.
¿Anular para hacer qué? ¿Cómo crear un objeto de estado asociado con un adaptador?
Phani Rithvij
Gracias amigo. Esta solución solo funciona
Parthi
20

Después de horas de frustración mientras intentaba todas las soluciones anteriores para superar este problema y también intentaba muchas soluciones en otras preguntas similares como esta , esto y esto que todos FALLARON conmigo para resolver este problema y hacer ViewPagerque destruya lo viejo Fragmenty lo llene pagerde el nuevo Fragments. He resuelto el problema de la siguiente manera:

1) Haga que la ViewPagerclase se extienda de la FragmentPagerAdaptersiguiente manera:

 public class myPagerAdapter extends FragmentPagerAdapter {

2) Cree un artículo para el ViewPagerque almacena el titley el fragmentsiguiente:

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) Haga que el constructor de ViewPagertake my FragmentManagerinstance lo almacene en mi de la classsiguiente manera:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

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

4) Crear un método para re-establecer los adapterdatos con los nuevos datos mediante la supresión de todos los anteriores fragmentde la fragmentManagermisma directamente a hacer el adapterpara establecer la nueva fragmentde la nueva lista de nuevo de la siguiente manera:

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) Desde el contenedor Activityo Fragmentno reinicialice el adaptador con los nuevos datos. Establezca los nuevos datos a través del método setPagerItemscon 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.

Sami Eltamawy
fuente
@Tomer, ¿cuál fue el problema que enfrentaste?
Sami Eltamawy
9

Tuve el mismo problema y mi solución está usando FragmentPagerAdapteranulando FragmentPagerAdapter#getItemId(int position):

@Override
public long getItemId(int position) {
    return mPages.get(position).getId();
}

Por defecto, este método devuelve la posición del artículo. Supongo que ViewPagercomprueba si itemIdse cambió y recrea la página solo si fue así. Pero la versión itemIdno anulada devuelve la misma posición que incluso si la página es realmente diferente, y ViewPager no define que la página se reemplaza por una y necesita ser recreada.

Para usar esto, long ides necesario para cada página. Normalmente se espera que sea único, pero sugiero, para este caso, que solo sea diferente del valor anterior para la misma página. Entonces, es posible usar un contador continuo en el adaptador o enteros aleatorios (con amplia distribución) aquí.

Creo que es una forma más consistente en lugar de usar las etiquetas de vista mencionadas como una solución en este tema. Pero probablemente no para todos los casos.

atlascoder
fuente
1
Está. Lo he comprobado: solo POSITION_NONE no es suficiente. Gracias por tu maravillosa respuesta! :)
Slava
Esta función ni siquiera está presente en PagerAdapter. Que clase es esta
Saket
@Saket, tienes razón, la clase esFragmentPagerAdapter
atlascoder
6

Encontré una decisión muy interesante de este problema. En lugar de usar FragmentPagerAdapter , que guarda en la memoria todos los fragmentos, podemos usar FragmentStatePagerAdapter ( android.support.v4.app.FragmentStatePagerAdapter ), que vuelve a cargar el fragmento cada vez que lo seleccionamos.

Las realizaciones de ambos adaptadores son idénticas. Entonces, solo necesitamos cambiar " extender FragmentPagerAdapter " en " extender FragmentStatePagerAdapter "

Рома Богдан
fuente
buen punto, ayudará a otros a resolver problemas de memoria de viewPager relacionados con el fragmento
Ashu Kumar
Gran respuesta, esto me ayudó mucho.
Sergio Marani
5

Después de mucho buscar este problema, encontré una solución realmente buena que creo que es la forma correcta de hacerlo. Esencialmente, instantiateItem solo se llama cuando la vista se instancia y nunca más a menos que la vista se destruya (esto es lo que sucede cuando se anula la función getItemPosition para devolver POSITION_NONE). En cambio, lo que desea hacer es guardar las vistas creadas y actualizarlas en el adaptador, generar una función get para que otra persona pueda actualizarla, o una función establecida que actualice el adaptador (mi favorito).

Entonces, en su MyViewPagerAdapter agregue una variable como:

private View updatableView;

An en su instancia de artículo:

 public Object instantiateItem(View collection, int position) {
        updatableView = new TextView(ctx); //My change is here
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);
        return view;
    }

así, de esta manera, puede crear una función que actualizará su vista:

public updateText(String txt)
{
    ((TextView)updatableView).setText(txt);
}

¡Espero que esto ayude!

alemán
fuente
Me di cuenta de que no estaba configurando los datos instantiateItem, por eso mis vistas no se actualizaban. De tu respuesta me di cuenta. +1
Phani Rithvij
5

Dos años y medio después de que el OP planteara su pregunta, este problema sigue siendo, bueno, sigue siendo 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 (vea la respuesta aceptada en esta publicación ). Una vez que se hizo evidente que el problema era que las páginas activas no se actualizaban correctamente, mi solución era obvia:

En mi Fragmento (las páginas):

  • Tomé todo el código que llena el formulario de onCreateView y lo puse en una función llamada PopulateForm que se puede llamar desde cualquier lugar, en lugar del 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; todos los demás códigos que crean oyentes FocusChange y similares aún están en OnCreate
  • Cree un booleano que se pueda usar como un indicador que indique que el formulario debe volver a cargarse. El mío es mbReloadForm
  • Anule 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, por lo que sé que dos o tres páginas se ven afectadas como máximo. Cambiar a la página 0 garantiza 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 manera, sabrá cuántas páginas se ven afectadas por el error. Si> 3, reduzca a 3: si está usando un Adaptador de Pager diferente, tendrá que ver con cuántas páginas tiene que lidiar (¿quizás todas?)
  • Vuelva a cargar 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 (). De lo contrario, configure el indicador ReloadForm.

Después de esto, cuando vuelva a cargar un segundo conjunto de páginas, el error aún hará 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 nunca fue incorrecta porque esta actualización ocurrirá antes de que vean la página.

¡Espero que esto ayude a alguien!

RichardP
fuente
5

Toda esta solución no me ayudó. Por lo tanto, encontré una solución que funciona: siempre puedes setAdapter, pero no es suficiente. debe hacer esto antes de cambiar el adaptador:

FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
    transaction.remove(f);
}
transaction.commit();

y después de esto:

viewPager.setAdapter(adapter);
Mahdi perfecto
fuente
¡Gracias! En mi caso, utilicé el ViewPagerfragmento interior, así que lo reemplacé slideShowPagerAdapter.getFragmentManager()con getChildFragmentManager(). Tal getFragmentManager()vez ayude en su caso. Yo solía FragmentPagerAdapter, no FragmentStatePagerAdapter. También vea stackoverflow.com/a/25994654/2914140 para una manera hacky.
CoolMind
5

Una forma mucho más fácil: use FragmentPagerAdapteray ajuste sus vistas paginadas en fragmentos. Se actualizan

alfongj
fuente
5

Gracias rui.araujo y Alvaro Luis Bustamante. Al principio, trato de usar rui.araujo, porque es fácil. Funciona, pero cuando cambian los datos, la página se volverá a dibujar obviamente. Es malo, así que trato de usar el camino de Álvaro Luis Bustamante. Es perfecto. Aquí está el código:

@Override
protected void onStart() {
    super.onStart();
}

private class TabPagerAdapter extends PagerAdapter {
    @Override
    public int getCount() {
        return 4;
    }

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

    @Override
    public void destroyItem(final View container, final int position, final Object object) {
        ((ViewPager) container).removeView((View) object);
    }

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View view = LayoutInflater.from(
                getBaseContext()).inflate(R.layout.activity_approval, null, false);
        container.addView(view);
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        view.setTag(position);
        new ShowContentListTask(listView, position).execute();
        return view;
    }
}

Y cuando los datos cambian:

for (int i = 0; i < 4; i++) {
    View view = contentViewPager.findViewWithTag(i);
    if (view != null) {
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        new ShowContentListTask(listView, i).execute();
    }
}
evan.z
fuente
4

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

En pseudocó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;
}
Jerry Tian
fuente
dentro de ViewPager, era bastante fácil saber la posición pasada del elemento, la implementación más perfecta es simplemente devolver la nueva posición getItemPosition()cuando el conjunto de datos cambió, y ViewPager sabrá que han cambiado o no. lamentablemente, ViewPager no lo hizo.
VinceStyling
3

Tuve un problema similar en el que tenía cuatro páginas y una de las páginas actualizaba las vistas de las otras tres. Pude actualizar los widgets (SeekBars, TextViews, etc.) en la página adyacente a la página actual. Las últimas dos páginas tendrían widgets no inicializados al llamar mTabsAdapter.getItem(position).

Para resolver mi problema, solía setSelectedPage(index)antes de llamargetItem(position) . Esto crearía una instancia de la página, lo que me permite poder modificar valores y widgets en cada página.

Después de toda la actualización que usaría setSelectedPage(position)seguido de notifyDataSetChanged().

Puede ver un ligero parpadeo en ListView en la página principal de actualización, pero no se nota nada. No lo he probado completamente, pero resuelve mi problema inmediato.

enKoder
fuente
Hola, estoy interesado en su código, tengo un problema similar, puedo actualizar fragmentos adyacentes pero el fragmento actual que está visible no se actualiza. Estoy muy confundido con todas las respuestas aquí. ¿Cómo puedo actualizar las vistas en el fragmento actual que muestra el adaptador VIewPager?
Pankaj Nimgade
3

Solo estoy publicando esta respuesta en caso de que alguien más lo encuentre útil. Para hacer exactamente lo mismo, simplemente tomé el código fuente de ViewPager y PagerAdapter de la biblioteca de compatibilidad y lo compilé dentro de mi código (debe resolver todos los errores e importarlos usted mismo, pero definitivamente se puede hacer).

Luego, en CustomViewPager, cree un método llamado updateViewAt (int position). La vista en sí se puede obtener de los elementos de ArrayList definidos en la clase ViewPager (debe establecer un Id. Para las vistas en el elemento instanciado y comparar este Id. Con la posición en el método updateViewAt ()). Luego puede actualizar la vista según sea necesario.

Naveen LP
fuente
2

Supongo que tengo las lógicas de ViewPager.

Si necesito actualizar un conjunto de páginas y mostrarlas en función del nuevo conjunto de datos, llamo a notifyDataSetChanged () . Luego, ViewPager realiza una serie de llamadas a getItemPosition () , pasando allí Fragment como Object. Este Fragmento puede ser de un conjunto de datos antiguo (que quiero descartar) o de uno nuevo (que quiero mostrar). Entonces, anulo getItemPosition () y allí tengo que determinar de alguna manera si mi Fragmento es del conjunto de datos anterior o del nuevo.

En mi caso, tengo un diseño de 2 paneles con una lista de elementos superiores en el panel izquierdo y una vista de deslizamiento (ViewPager) a la derecha. Por lo tanto, guardo un enlace a mi elemento superior actual dentro de mi Adaptador Pager y también dentro de cada Fragmento de página instanciado. Cuando cambia el elemento superior seleccionado en la lista, almaceno el nuevo elemento superior en PagerAdapter y llamo a notifyDataSetChanged () . Y en el getItemPosition () reemplazado , comparo el elemento superior de mi adaptador con el elemento superior de mi fragmento. Y solo si no son iguales, devuelvo POSITION_NONE. Luego, PagerAdapter reinstala todos los fragmentos que han devuelto POSITION_NONE.

NOTA.Almacenar la identificación del elemento superior en lugar de una referencia podría ser una mejor idea.

El fragmento de código a continuación es un poco esquemático, pero lo adapté del código que realmente funciona.

public class SomeFragment extends Fragment {
  private TopItem topItem;
}

public class SomePagerAdapter extends FragmentStatePagerAdapter {
  private TopItem topItem;

  public void changeTopItem(TopItem newTopItem) {
    topItem = newTopItem;
    notifyDataSetChanged();
  }

  @Override
  public int getItemPosition(Object object) {
    if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
      return POSITION_NONE;
    }
    return super.getItemPosition(object);
  }
}

¡Gracias por todos los investigadores anteriores!

Stanislav Karakhanov
fuente
2

El siguiente código funcionó para mí.

Cree una clase que extienda la clase FragmentPagerAdapter como se muestra a continuación.

public class Adapter extends FragmentPagerAdapter {

private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;

public Adapter(FragmentManager fm) {
    super(fm);
}

public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
    super(fm);
    mActivity = mA;
    mFragmentManager = fm;
    object = new ArrayList<>();
    mFragmentTags = new HashMap<Integer, String>();
    this.tabCount = numberOfTabs;
}

@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return Fragment0.newInstance(mActivity);
        case 1:
            return Fragment1.newInstance(mActivity);
        case 2:
            return Fragment2.newInstance(mActivity);
        default:
            return null;
    }}


@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Log.e("Already defined","Yes");
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        Log.e("Fragment Tag","" + position + ", " + tag);
        mFragmentTags.put(position, tag);
    }else{
        Log.e("Already defined","No");
    }
    container_id = container.getId();
    this.container = container;
    if(position == 0){
        this.object.add(0,object);
    }else if(position == 1){
        this.object.add(1,object);
    }else if(position == 2){
        this.object.add(2,object);
    }
    return object;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    if (object instanceof Fragment) {
        Log.e("Removed" , String.valueOf(position));
    }
}

@Override
public int getItemPosition (Object object)
{   int index = 0;
    if(this.object.get(0) == object){
        index = 0;
    }else if(this.object.get(1) == object){
        index = 1;
    }else if(this.object.get(2) == object){
        index = 2;
    }else{
        index = -1;
    }
    Log.e("Index" , "..................." + String.valueOf(index));
    if (index == -1)
        return POSITION_NONE;
    else
        return index;
}

public String getFragmentTag(int pos){
    return "android:switcher:"+R.id.pager+":"+pos;
}

public void NotifyDataChange(){
    this.notifyDataSetChanged();
}

public int getcontainerId(){
    return container_id;
}

public ViewGroup getContainer(){
    return this.container;
}

public List<Object> getObject(){
    return this.object;
}

@Override
public int getCount() {
    return tabCount;
}}

Luego, dentro de cada Fragmento que creaste, crea un método updateFragment. En este método, cambia las cosas que necesita cambiar en el fragmento. Por ejemplo, en mi caso, Fragment0 contenía un GLSurfaceView que muestra un objeto 3D basado en una ruta a un archivo .ply, por lo que dentro de mi método updateFragment cambio la ruta a este archivo de capas.

luego cree una instancia de ViewPager,

viewPager = (ViewPager) findViewById(R.id.pager);

y una instancia de Adpater,

adapter = new Adapter(getSupportFragmentManager(), 3, this);

entonces haz esto,

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);

Luego, dentro de la clase, inicializaste la clase Adaptador anterior y creaste un viewPager, cada vez que quieras actualizar uno de tus fragmentos (en nuestro caso Fragment0) usa lo siguiente:

adapter.NotifyDataChange();

adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.

fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.

fragment0.updateFragment() method which include the updates on this fragment

adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.

Esta solución se basó en la técnica sugerida por Alvaro Luis Bustamante.

Brainnovo
fuente
1

1.Primero debe configurar el método getItemposition en su clase Pageradapter 2.Debe leer la posición Exacta de su Visualizador de páginas 3.A continuación, envíe esa posición como ubicación de datos de su nuevo 4.Escriba el botón de actualización haciendo clic en el oyente dentro del setonPageChange oyente

ese código de programa se modificó un poco para configurar solo el elemento de posición particular

public class MyActivity extends Activity {

private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

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

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

      updateButton = (Button) findViewById(R.id.update);

    myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int i, float v, int i2) {
             //Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPageSelected( int i) {
          // here you will get the position of selected page
            final int k = i;
             updateViewPager(k);

        }

        @Override
        public void onPageScrollStateChanged(int i) {

        }
    });
}

private void updateViewPager(final int i) {  
    updateButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
            data.set(i, "Replaced "+i);         
            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 int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @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) {
    }
}
}
Naveen Kumar
fuente
1

lo que funcionó para mí iba viewPager.getAdapter().notifyDataSetChanged();

y en el adaptador colocando su código para actualizar la vista dentro de esta getItemPositionmanera

@Override
public int getItemPosition(Object object) {

    if (object instanceof YourViewInViewPagerClass) { 
        YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
        view.setData(data);
    }

    return super.getItemPosition(object);
}

Puede que no sea la forma más correcta de hacerlo, pero funcionó (el return POSITION_NONEtruco me causó un colapso, así que no era una opción)

Fonix
fuente
1

Puede actualizar dinámicamente todos los fragmentos, puede ver en tres pasos.

En tu adaptador:

public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;

public MyPagerAdapter(FragmentManager fragmentManager) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mFragmentTags = new HashMap<Integer, String>();
}

// Returns total number of pages
@Override
public int getCount() {
    return NUM_ITEMS;
}

// Returns the fragment to display for that page
@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return FirstFragment.newInstance();
        case 1:
            return SecondFragment.newInstance();
        case 2:
            return ThirdFragment.newInstance();
        default:
            return null;
    }
}

// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
    return "Page " + position;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        mFragmentTags.put(position, tag);
    }
    return object;
}

public Fragment getFragment(int position) {
    Fragment fragment = null;
    String tag = mFragmentTags.get(position);
    if (tag != null) {
        fragment = mFragmentManager.findFragmentByTag(tag);
    }
    return fragment;
}}

Ahora en tu actividad:

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{

MyPagerAdapter mAdapterViewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
    mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
    viewPager.setAdapter(mAdapterViewPager);
    viewPager.addOnPageChangeListener(this);
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {

    Fragment fragment = mAdapterViewPager.getFragment(position);
    if (fragment != null) {
        fragment.onResume();
    }
}

@Override
public void onPageScrollStateChanged(int state) {

}}

Finalmente en tu fragmento, algo así:

public class YourFragment extends Fragment {

// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {

    return new YourFragment();
}

// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment, container, false);
}


@Override
public void onResume() {
    super.onResume();

    //to refresh your view
    refresh();

}}

Puedes ver el código completo aquí .

Gracias Alvaro Luis Bustamante.

Cabezas
fuente
1

Siempre volviendo POSITION_NONE es simple pero un poco ineficiente porque evoca la creación de instancias de todas las páginas que ya se han creado.

He creado una biblioteca ArrayPagerAdapter para cambiar elementos en PagerAdapters dinámicamente.

Internamente, los adaptadores de esta biblioteca regresan POSITION_NONEengetItemPosiition() sólo cuando sea necesario.

Puede cambiar elementos dinámicamente como los siguientes utilizando esta biblioteca.

@Override
protected void onCreate(Bundle savedInstanceState) {
        /** ... **/
    adapter = new MyStatePagerAdapter(getSupportFragmentManager()
                            , new String[]{"1", "2", "3"});
    ((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
     adapter.add("4");
     adapter.remove(0);
}

class MyPagerAdapter extends ArrayViewPagerAdapter<String> {

    public MyPagerAdapter(String[] data) {
        super(data);
    }

    @Override
    public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
        View v = inflater.inflate(R.layout.item_page, container, false);
        ((TextView) v.findViewById(R.id.item_txt)).setText(item);
        return v;
    }
}

La biblioteca Thils también admite páginas creadas por Fragments.

T.Nakama
fuente
1

Esto es para todos aquellos como yo, que necesitan actualizar el Viewpager desde un servicio (u otro hilo de fondo) y ninguna de las propuestas ha funcionado: después de un poco de verificación, me di cuenta de que el método notifyDataSetChanged () nunca regresa. getItemPosition (Object object) se llama all all allí sin procesamiento adicional. Luego encontré en los documentos de la clase padre PagerAdapter (no está en los documentos de las subclases), "Los cambios en el conjunto de datos deben ocurrir en el hilo principal y deben terminar con una llamada a notifyDataSetChanged ()". Entonces, la solución de trabajo en este caso fue (usando FragmentStatePagerAdapter y getItemPosition (Object object) configurado para devolver POSITION_NONE):

y luego la llamada a notifyDataSetChanged ():

runOnUiThread(new Runnable() {
         @Override
         public void run() {
             pager.getAdapter().notifyDataSetChanged();
         }
     });
Helmwag
fuente
1

Puede agregar la transformación de buscapersonas en Viewpager de esta manera

myPager.setPageTransformer(true, new MapPagerTransform());

En el siguiente código, cambié el color de mi vista en tiempo de ejecución cuando se desplaza el localizador

public class MapPagerTransform implements ViewPager.PageTransformer {


    public void transformPage(View view, float position) {
     LinearLayout  showSelectionLL=(LinearLayout)view.findViewById(R.id.showSelectionLL);

     if (position < 0) {

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else if (position >0){

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else {
                showSelectionLL.setBackgroundColor(Color.RED);
     }
   }
}
Costa Akshay
fuente
1

Sé que llego tarde pero aún así puede ayudar a alguien. Solo estoy extendiendo la respuesta acertada y también he agregado el comentario al respecto.

bien,

la respuesta en sí dice que es ineficiente

así que para que se actualice solo cuando sea necesario, puede hacer esto

private boolean refresh;

public void refreshAdapter(){
    refresh = true;
    notifyDataSetChanged();
}

@Override
public int getItemPosition(@NonNull Object object) {
    if(refresh){
        refresh = false;
        return POSITION_NONE;
    }else{
        return super.getItemPosition(object);
    }
}
ejemplo
fuente
1

ViewPager no fue diseñado para admitir cambios dinámicos de vista.

Tuve confirmación de esto mientras buscaba otro error relacionado con este https://issuetracker.google.com/issues/36956111 y en particular https://issuetracker.google.com/issues/36956111#comment56

Esta pregunta es un poco antigua, pero Google recientemente resolvió este problema con ViewPager2 . Permitirá reemplazar las soluciones hechas a mano (sin mantenimiento y potencialmente defectuosas) por soluciones estándar. También evita recrear vistas innecesariamente como lo hacen algunas respuestas.

Para ver ejemplos de ViewPager2, puede consultar https://github.com/googlesamples/android-viewpager2

Si desea usar ViewPager2, deberá agregar la siguiente dependencia en su archivo build.gradle:

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }

Luego puede reemplazar su ViewPager en su archivo xml con:

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

Después de eso, deberá reemplazar ViewPager por ViewPager2 en su actividad

ViewPager2 necesita un RecyclerView.Adapter o un FragmentStateAdapter, en su caso puede ser un RecyclerView.Adapter

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private Context context;
    private ArrayList<String> arrayList = new ArrayList<>();

    public MyAdapter(Context context, ArrayList<String> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tvName.setText(arrayList.get(position));
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tvName);
        }
    }
} 

En el caso de que estuviera usando un TabLayout, puede usar un TabLayoutMediator:

        TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
            @Override
            public void onConfigureTab(@NotNull TabLayout.Tab tab, int position) {
                // configure your tab here
                tab.setText(tabs.get(position).getTitle());
            }
        });

        tabLayoutMediator.attach();

Luego podrá actualizar sus vistas modificando los datos de su adaptador y llamando al método notifyDataSetChanged

Yves Delerm
fuente
0

Creo que he hecho una manera simple de notificar los cambios en el conjunto de datos:

Primero, cambie un poco la forma en que funciona la función instantiateItem:

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View rootView = mInflater.inflate(...,container, false);
        rootView.setTag(position);
        updateView(rootView, position);
        container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mViewPager.setObjectForPosition(rootView, position);
        return rootView;
    }

para "updateView", llene la vista con todos los datos que desea llenar (setText, setBitmapImage, ...).

Verifique que destroyView funcione así:

    @Override
    public void destroyItem(final ViewGroup container, final int position, final Object obj) {
        final View viewToRemove = (View) obj;
        mViewPager.removeView(viewToRemove);
    }

Ahora, suponga que necesita cambiar los datos, hacerlo y luego llamar a la siguiente función en el Adaptador de Pager:

    public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
            final NotifyLocation toPos) {
        final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
        final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        if (fromPosInt <= toPosInt) {
            notifyDataSetChanged();
            for (int i = fromPosInt; i <= toPosInt; ++i) {
                final View pageView = viewPager.findViewWithTag(i);
                mPagerAdapter.updateView(pageView, i);
            }
        }
    }

public enum NotifyLocation {
    MOST_LEFT, CENTER, MOST_RIGHT
}

Por ejemplo, si desea notificar a todas las vistas que viewPager muestra que algo ha cambiado, puede llamar a:

notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);

Eso es.

desarrollador de Android
fuente
0

Por lo que vale, en KitKat + parece que adapter.notifyDataSetChanged()es suficiente para que aparezcan las nuevas vistas, siempre que sea lo setOffscreenPageLimitsuficientemente alto. Soy capaz de obtener el comportamiento deseado haciendo viewPager.setOffscreenPageLimit(2).

mszaro
fuente
0

En realidad yo uso notifyDataSetChanged()en ViewPagery CirclePageIndicator, y después de eso que llamo destroyDrawingCache()el ViewPagery funciona .. Ninguna de las otras soluciones que funcionó para mí.

czaku
fuente
La pregunta original tiene casi 2 años, estoy seguro de que hay muchas diferencias en la API de Android que las respuestas aquí pueden no ser adecuadas para todas las versiones de la API de Android.
C0deAttack