android - studio - ViewPager PagerAdapter no actualiza la vista
view pager adapter android studio (30)
Estoy usando el ViewPager de la biblioteca de compatibilidad. Lo obtuve con éxito mostrando varias vistas con las que puedo hojear.
Sin embargo, me resulta difícil descubrir cómo actualizar el ViewPager con un nuevo conjunto de Vistas.
He intentado todo tipo de cosas como llamar a mAdapter.notifyDataSetChanged()
, mViewPager.invalidate()
incluso creando un adaptador completamente nuevo cada vez que quiero usar una nueva Lista de datos.
Nada ha ayudado, las vistas de texto permanecen sin cambios respecto a los datos originales.
Actualización: hice un pequeño proyecto de prueba y casi he podido actualizar las vistas. Voy a pegar la clase de abajo.
Lo que no parece actualizarse, sin embargo, 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) {
}
}
}
Cambie el FragmentPagerAdapter
a FragmentStatePagerAdapter
.
getItemPosition()
método getItemPosition()
y devuelva POSITION_NONE
.
Con el tiempo, escuchará la notifyDataSetChanged()
en el paginador de la vista.
Después de horas de frustración mientras intentaba todas las soluciones anteriores para superar este problema y también probé muchas soluciones en otras preguntas similares como this , this y this que FALLÓ conmigo para resolver este problema y hacer que ViewPager
destruya el Fragment
antiguo y lo rellene El pager
con el nuevo Fragment
s. He resuelto el problema de la siguiente manera:
1) Haga que la clase ViewPager
amplíe FragmentPagerAdapter
siguiente manera:
public class myPagerAdapter extends FragmentPagerAdapter {
2) Cree un elemento para el ViewPager
que almacene el title
y el fragment
siguiente manera:
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 que el constructor del ViewPager
tome mi instancia de FragmentManager
para almacenarlo en mi class
siguiente manera:
private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;
public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mPagerItems = pagerItems;
}
4) Cree un método para restablecer los datos del adapter
con los nuevos datos eliminando todo el fragment
anterior del fragmentManager
directamente para hacer que el adapter
establezca el nuevo fragment
de la nueva lista 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 Activity
o Fragment
no reinicialice el adaptador con los nuevos datos. Establezca 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.
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 recibe una llamada cuando la vista se crea una instancia y nunca más a menos que la vista se destruya (esto es lo que sucede cuando anula la función getItemPosition para devolver POSITION_NONE). En su lugar, lo que desea hacer es guardar las vistas creadas y actualizarlas en el adaptador, generar una función de obtención para que alguien más pueda actualizarlas o una función de configuración que actualice el adaptador (mi favorito).
Entonces, en su MyViewPagerAdapter agregue una variable como:
private View updatableView;
una en su instancia
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;
}
De esta manera, puede crear una función que actualizará su vista:
public updateText(String txt)
{
((TextView)updatableView).setText(txt);
}
¡Espero que esto ayude!
Dos años y medio después de que el OP formuló 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 fue la causa real del problema (consulte la respuesta aceptada en este post ). Una vez que fue evidente que el problema era que las páginas activas no se actualizaban correctamente, mi solución fue obvia:
En mi Fragmento (las páginas):
- Tomé todo el código que llena la forma de onCreateView y lo puse en una función llamada PopulateForm que puede llamarse desde cualquier lugar, en lugar de hacerlo por 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; todo el otro código que crea los escuchas de FocusChange y similares todavía está en OnCreate
- Cree un booleano que se pueda usar como un indicador que indique que se debe volver a cargar el formulario. El mio es mbReloadForm
- Reemplace OnResume () para llamar a PopulateForm () si mbReloadForm está configurado.
En mi actividad, donde hago la carga de las páginas:
- Ve a la página 0 antes de cambiar nada. Estoy usando FragmentStatePagerAdapter, así que sé que dos o tres páginas están afectadas como máximo. Cambiar a la página 0 asegura que solo tengo el problema en las páginas 0, 1 y 2.
- Antes de borrar la lista anterior, tome su tamaño (). De esta manera usted sabe cuántas páginas se ven afectadas por el error. Si> 3, redúzcalo a 3; si está utilizando un PagerAdapter diferente, tendrá que ver con 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 utilizando pager.getChildAt (i): esto le indica si tiene una vista. Si es así, llame a pager.PopulateView (). Si no es así, establezca el indicador ReloadForm.
Después de esto, cuando vuelva a cargar un segundo conjunto de páginas, el error todavía 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!
En caso de que alguien use el adaptador basado en FragmentStatePagerAdapter (que permitirá a ViewPager crear el mínimo de páginas necesario 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, puede ser mejorado.
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;
}
Este es un problema horrible y me complace presentar una excelente solución; Simple, eficiente y eficaz!
Vea a continuación, el código muestra el uso de una bandera para indicar cuándo devolver POSITION_NONE
public class ViewPagerAdapter extends PagerAdapter
{
// Members
private boolean mForceReinstantiateItem = false;
// This is used to overcome terrible bug that Google isn''t fixing
// We know that getItemPosition() is called right after notifyDataSetChanged()
// Therefore, the fix is to return POSITION_NONE right after the notifyDataSetChanged() was called - but only once
@Override
public int getItemPosition(Object object)
{
if (mForceReinstantiateItem)
{
mForceReinstantiateItem = false;
return POSITION_NONE;
}
else
{
return super.getItemPosition(object);
}
}
public void setData(ArrayList<DisplayContent> newContent)
{
mDisplayContent = newContent;
mForceReinstantiateItem = true;
notifyDataSetChanged();
}
}
Gracias rui.araujo y Alvaro Luis Bustamante. Al principio, trato de usar el camino de 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();
}
}
Hay varias maneras de lograr esto.
La primera opción es más fácil, pero un poco más ineficiente.
Reemplace getItemPosition
en su PagerAdapter
esta manera:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
De esta forma, cuando llame a notifyDataSetChanged()
, el paginador de vista eliminará todas las vistas y las volverá a cargar. Como tal se obtiene el efecto de recarga.
La segunda opción, sugerida por Alvaro Luis Bustamante (anteriormente alvarolb) , es setTag()
método setTag()
en el método de instantiateItem()
al 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.
Me pareció muy interesante la decisión 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. Por lo tanto, solo necesitamos cambiar " extender FragmentPagerAdapter " en " extender FragmentStatePagerAdapter "
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 inadecuado de las vistas creadas desde mi punto de vista.
Los últimos días he estado trabajando con PagerAdapter
y ViewPager
, y encontré lo siguiente:
El método notifyDataSetChanged()
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 ViewPager
determina si se debe eliminar o crear una nueva vista con los getItemPosition()
y getCount()
.
Creo que ViewPager
, después de una llamada notifyDataSetChanged()
toma sus vistas secundarias y comprueba su posición con getItemPosition()
. Si para una vista secundaria este método devuelve POSITION_NONE
, el ViewPager
entiende que la vista se ha eliminado, llamando al destroyItem()
y eliminando esta vista.
De esta manera, anular getItemPosition()
para devolver siempre POSITION_NONE
es completamente incorrecto si solo desea actualizar el contenido de las páginas, ya que las vistas creadas previamente se destruirán y las nuevas se crearán cada vez que llame a notifyDatasetChanged()
. Puede parecer que no está tan mal solo para unos pocos TextView
s, pero cuando tiene vistas complejas, como ListViews completadas desde una base de datos, esto puede ser un problema real y una pérdida de recursos.
Así que hay varios enfoques para cambiar de manera eficiente el contenido de una vista sin tener que eliminar y crear una instancia de la vista nuevamente. Depende del problema que quieras resolver. Mi enfoque es utilizar el método setTag()
para cualquier vista instanciada en el método instantiateItem()
. Entonces, cuando desee cambiar los datos o invalidar la vista que necesita, puede llamar al método findViewWithTag()
en el ViewPager
para recuperar la vista instanciada anteriormente y modificarla / usarla como desee sin tener que eliminar / crear una nueva vista. Cada vez que quieras actualizar algún valor.
Imagine, por ejemplo, que tiene 100 páginas con 100 TextView
y solo desea actualizar un valor periódicamente. Con los enfoques explicados anteriormente, esto significa que está eliminando y creando una instancia de 100 TextView
en cada actualización. No tiene sentido...
Puedes actualizar dinámicamente todos los fragmentos, puedes verlos en tres pasos.
En su 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 here .
Gracias Álvaro Luis Bustamante.
Siempre devolver POSITION_NONE
es una forma simple pero poco eficiente porque evoca la creación de instancias de todas las páginas que ya se han instanciado.
He creado una biblioteca ArrayPagerAdapter para cambiar elementos en PagerAdapters dinámicamente.
Internamente, los adaptadores de esta biblioteca devuelven POSITION_NONE
en getItemPosiition()
solo cuando es necesario.
Puede cambiar elementos dinámicamente como siguiendo 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 de Thils también soporta páginas creadas por Fragmentos.
Solo publico esta respuesta en caso de que alguien más la 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 importar usted mismo, pero definitivamente se puede hacer).
Luego, en el CustomViewPager, cree un método llamado updateViewAt (posición int). La vista en sí misma puede obtenerse de los elementos de ArrayList definidos en la clase ViewPager (debe establecer una identificación para las vistas en el elemento de instancia y comparar esta identificación con la posición en el método updateViewAt ()). A continuación, puede actualizar la vista según sea necesario.
Supongo, tengo las lógicas de ViewPager.
Si necesito actualizar un conjunto de páginas y mostrarlas según el nuevo conjunto de datos, llamo a notificationDataSetChanged () . Luego, ViewPager realiza una serie de llamadas a getItemPosition () , pasando allí Fragmento como un Objeto. Este fragmento puede ser de un conjunto de datos antiguo (que deseo descartar) o de uno nuevo (que deseo mostrar). Entonces, anulo getItemPosition () y ahí 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 los elementos principales en el panel izquierdo y una vista deslizable (ViewPager) a la derecha. Por lo tanto, almaceno un enlace a mi elemento superior actual dentro de mi PagerAdapter y también dentro de cada fragmento de página instanciada. Cuando el elemento superior seleccionado en la lista cambia, almaceno el nuevo elemento superior en PagerAdapter y llamo a NotifyDataSetChanged () . Y en el getItemPosition () anulado, 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!
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) {
}
Todas estas soluciones no me ayudaron. Por lo tanto, encontré una solución de trabajo: puedes setAdapter
cada vez, pero no es suficiente. Debes 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);
Tuve el mismo problema y mi solución está anulando a ViewPagerAdapter#getItemId(int position)
:
@Override
public long getItemId(int position) {
return mPages.get(position).getId();
}
De forma predeterminada, este método devuelve la posición del elemento. Supongo que ViewPager
comprueba si itemId
se cambió y itemId
crear la página solo si lo fue. Pero la versión no itemId
devuelve la misma posición que itemId
incluso si la página es realmente diferente, y ViewPager no define que la página sea reemplazada y deba volver a crearse.
Para usar esto, se necesita una long id
para cada página. Normalmente se espera que sea único, pero sugiero, para este caso, que debería ser diferente del valor anterior para la misma página. Por lo tanto, es posible usar un contador continuo en adaptadores o enteros aleatorios (con una amplia distribución) aquí.
Creo que es una forma más consistente en lugar de utilizar las etiquetas de vista mencionadas como una solución en este tema. Pero probablemente no para todos los casos.
Tuve un problema similar en el que tenía cuatro páginas y una de las páginas actualizó las vistas de las otras tres. Pude actualizar los widgets (SeekBars, TextViews, etc.) en la página adyacente a la página actual. Las dos últimas páginas tendrían widgets sin inicializar al llamar a mTabsAdapter.getItem(position)
.
Para resolver mi problema, utilicé setSelectedPage(index)
antes de llamar a getItem(position)
. Esto crearía una instancia de la página, permitiéndome poder alterar los valores y los widgets en cada página.
Después de toda la actualización, usaría setSelectedPage(position)
seguido de notifyDataSetChanged()
.
Puede ver un ligero parpadeo en el ListView en la página de actualización principal, pero no se nota nada. No lo he probado a fondo, pero resuelve mi problema inmediato.
Una forma mucho más fácil: use un FragmentPagerAdapter
y envuelva sus vistas paginadas en fragmentos. Ellos se actualizan
lo que funcionó para mí fue viewPager.getAdapter().notifyDataSetChanged();
y en el adaptador, poniendo su código para actualizar la vista dentro de getItemPosition
como tal
@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 truco de return POSITION_NONE
bloqueó para mí, así que no era una opción)
1. Primero debe configurar el método getItemposition en su clase de adaptador de página 2. Debe leer la posición exacta de su View Pager 3. luego enviar esa posición como ubicación de datos de su nuevo botón 4.Write update onclick listener dentro del setonPageChange oyente
ese código de programa es poco lo modifiqué para establecer 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) {
}
}
}
Creo que he hecho una forma sencilla 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", complete 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 PagerAdapter:
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 mostradas por viewPager que algo ha cambiado, puede llamar a:
notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);
Eso es.
El siguiente código funcionó para mí.
Cree una clase que amplíe 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 cambias las cosas que necesitas 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, así que dentro de mi método updateFragment, cambio la ruta a este archivo de capas.
luego crea 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 en la que inicializó la clase Adaptador anterior y creó un viewPager, cada vez que quiera actualizar uno de sus fragmentos (en nuestro caso Fragment0) use 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 Álvaro Luis Bustamante.
La respuesta dada por alvarolb es definitivamente la mejor manera de hacerlo. Sobre la base de 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 que reemplaza el método notifyDataSetChanged
, 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();
}
Puede usar código similar en instantiateItem
y notifyDataSetChanged
para actualizar su vista. En mi código utilizo exactamente el mismo método.
En lugar de volver POSITION_NONE
y crear todos los fragmentos de nuevo, puede hacer lo que sugerí aquí: ¿ Actualizar ViewPager dinámicamente?
En realidad yo uso notifyDataSetChanged()
en ViewPager
y CirclePageIndicator
, y después de eso que llamo destroyDrawingCache()
el ViewPager
y funciona .. Ninguna de las otras soluciones que funcionó para mí.
Esto es para todos aquellos que, como yo, necesitan actualizar el Viewpager desde un servicio (u otro subproceso en segundo plano) y ninguna de las propuestas ha funcionado. getItemPosition (Objeto de objeto) se denomina todos los extremos allí sin procesamiento adicional. Luego encontré en los documentos de la clase de PagerAdapter principal (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 ()". Por lo tanto, la solución de trabajo en este caso fue (usando FragmentStatePagerAdapter y getItemPosition (objeto objeto) configurado para devolver POSITION_NONE):
y luego la llamada a notifyDataSetChanged ():
runOnUiThread(new Runnable() {
@Override
public void run() {
pager.getAdapter().notifyDataSetChanged();
}
});
He intentado que todas estas soluciones no funcionaron para mí porque todas mis opiniones son complejas. Por lo tanto, es difícil guardar la Vista y actualizar la Vista. Si tiene una Vista simple que contiene solo unos pocos TextView o algo así, es fácil y las soluciones publicadas aquí funcionan. Pero si tienes una vista compleja como la mía, y necesitas cambiar todo el Fragmento, creo que es imposible hacerlo. He investigado el código de ViewPager y parece que ViewPager conserva los fragmentos antiguos. Es fácil darse cuenta: busque alguna aplicación nativa desde su dispositivo que implemente deslizar y haga la prueba cambiando el idioma de inglés a árabe. En este caso, debería cambiar el orden de los fragmentos, porque el árabe es un lenguaje RTL. Pero no funcionó de esta manera.
Para lo que vale, en KitKat + parece que adapter.notifyDataSetChanged()
es suficiente para hacer que aparezcan las nuevas vistas, siempre que tengas setOffscreenPageLimit
suficiente altura. Soy capaz de conseguir el comportamiento deseado haciendo viewPager.setOffscreenPageLimit(2)
.
Si desea resolver un Viewpager
problema de actualización, revise este proyecto, resolverá su problema.
y más comprenda compruebe eso;
feliz codificación ...