ViewPager PagerAdapter non aggiorna la vista


597

Sto usando ViewPager dalla libreria di compatibilità. Ho ottenuto con successo la visualizzazione di diverse visualizzazioni che posso sfogliare.

Tuttavia, faccio fatica a capire come aggiornare ViewPager con un nuovo set di visualizzazioni.

Ho provato ogni sorta di cose come chiamare mAdapter.notifyDataSetChanged(), mViewPager.invalidate()anche creando un adattatore nuovo di zecca ogni volta che voglio usare un nuovo elenco di dati.

Nulla ha aiutato, le visualizzazioni di testo rimangono invariate rispetto ai dati originali.

Aggiornamento: ho realizzato un piccolo progetto di test e sono quasi riuscito ad aggiornare le visualizzazioni. Incollerò la classe qui sotto.

Ciò che non sembra aggiornare, tuttavia, è la seconda vista, la 'B' rimane, dovrebbe visualizzare 'Y' dopo aver premuto il pulsante di aggiornamento.

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

1
Consultare stackoverflow.com/questions/12510404/… per un potenziale bug con FragmentStatePagerAdapter.
Samis,

2
Mi dispiace dragare questo. La tua domanda mi ha davvero aiutato, quindi grazie! L'unica cosa che non capisco è come viene modificato il riferimento ai dati nel PagerAdapter quando lo aggiorni
nell'Attività

Fai la tua adattatore estendere BaseAdapter Suggerire questo: stackoverflow.com/questions/13664155/...

1
Apparentemente se si imposta nuovamente l'adattatore sul pager della vista, si reimposta.
EpicPandaForce,

ho un problema con il cercapersone stackoverflow.com/questions/41217237/…
chris

Risposte:


856

Esistono diversi modi per raggiungere questo obiettivo.

La prima opzione è più semplice, ma un po 'più inefficiente.

Sostituisci getItemPositionin PagerAdapterquesto modo:

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

In questo modo, quando si chiama notifyDataSetChanged(), il cercapersone delle viste rimuoverà tutte le viste e le ricaricherà tutte. In questo modo si ottiene l'effetto di ricarica.

La seconda opzione, suggerita da Alvaro Luis Bustamante (precedentemente alvarolb) , è quella di eseguire il setTag()metodo instantiateItem()quando si crea un'istanza di una nuova vista. Quindi, invece di utilizzare notifyDataSetChanged(), è possibile utilizzare findViewWithTag()per trovare la vista che si desidera aggiornare.

Il secondo approccio è molto flessibile e ad alte prestazioni. Complimenti ad alvarolb per la ricerca originale.


4
Meglio del mio lavoro in giro, sembra non avere alcun effetto collaterale, grazie.
C0de Attacco

3
Ho un problema simile. Il mio viewPager mostrerà i nuovi risultati di ricerca ma non torna alla prima voce del cursore. Qualcuno sa come ripristinare la posizione durante l'aggiornamento del set di dati?
mAndroid,

1
@mAndroid dovresti presentare una domanda separata con maggiori dettagli.
rui.araujo,

1
la restituzione di POSITION_NONE apparentemente aggiunge molta inefficienza all'applicazione complessiva in quanto ciò fa sì che Android elimini la vista associata alla posizione richiesta. È quindi necessario ricreare nuovamente la vista prima di visualizzarla. Risolve il problema delle visualizzazioni non aggiornate, ma probabilmente non è la soluzione migliore per layout complessi.
wufoo,

11
Meglio risposta (senza rimuovere tutte le voci) -> stackoverflow.com/a/10852046/898056
yhpark

484

Non credo che ci sia alcun tipo di bug nel file PagerAdapter. Il problema è che capire come funziona è un po 'complesso. Guardando le soluzioni spiegate qui, c'è un malinteso e quindi un cattivo uso delle viste istanziate dal mio punto di vista.

Negli ultimi giorni ho lavorato con PagerAdaptere ViewPager, e ho trovato quanto segue:

Il notifyDataSetChanged()metodo su PagerAdapternotificherà solo ViewPagerche le pagine sottostanti sono cambiate. Ad esempio, se hai creato / eliminato pagine in modo dinamico (aggiungendo o rimuovendo elementi dal tuo elenco), ViewPagerdovresti occupartene. In questo caso, penso che ViewPagerdetermina se una nuova vista debba essere cancellata o istanziata usando i metodi getItemPosition()e getCount().

Penso che ViewPager, dopo una notifyDataSetChanged()chiamata, prende le viste secondarie e ne controlla la posizione con il getItemPosition(). Se per una vista figlio questo metodo ritorna POSITION_NONE, ViewPagercapisce che la vista è stata eliminata, chiamando il destroyItem()e rimuovendo questa vista.

In questo modo, l'override getItemPosition()di restituire sempre POSITION_NONEè completamente errato se si desidera aggiornare solo il contenuto delle pagine, poiché le viste create in precedenza verranno distrutte e verranno create nuove ogni volta che si chiama notifyDatasetChanged(). Può sembrare che non sia così sbagliato solo per alcuni TextViewsecondi, ma quando hai viste complesse, come ListViews popolate da un database, questo può essere un vero problema e uno spreco di risorse.

Esistono quindi diversi approcci per modificare in modo efficiente il contenuto di una vista senza dover rimuovere e creare nuovamente un'istanza della vista. Dipende dal problema che si desidera risolvere. Il mio approccio è utilizzare il setTag()metodo per qualsiasi vista istanziata nel instantiateItem()metodo. Pertanto, quando si desidera modificare i dati o invalidare la vista necessaria, è possibile chiamare il findViewWithTag()metodo su ViewPagerper recuperare la vista istanziata in precedenza e modificarla / utilizzarla come desiderato senza dover eliminare / creare una nuova vista ogni volta che si desidera per aggiornare un po 'di valore.

Ad esempio, immagina di avere 100 pagine con 100 TextViewse desideri aggiornare periodicamente un solo valore. Con gli approcci spiegati in precedenza, questo significa che stai rimuovendo e creando un'istanza di 100 TextViews su ogni aggiornamento. Non ha senso...


11
+1 - La tua soluzione non funziona solo come un incantesimo che ho riscontrato esattamente nel problema che hai descritto qui quando ho dovuto aggiornare le pagine che contenevano schede con visualizzazioni di elenco, visualizzazioni di testo e immagini. (OutOfErrorExceptions e simili ...) Grazie!
schlingel,

3
@alvarolb: Ciao, non so davvero cosa intendi per setTagmetodo onInstantiateItem, Vuoi aggiornare questa risposta con un po 'di codice? Grazie.
Torre Huy,

35
Sarebbe perfetto se tu scrivessi un esempio.
Sami Eltamawy,

13
Come dice qualcuno: un esempio sarà apprezzato!
Jey10

15
6 anni ormai, nessuno può fornire un esempio.
Oussaki,

80

Cambia FragmentPagerAdapterin FragmentStatePagerAdapter.

Sostituisci getItemPosition()metodo e restituisci POSITION_NONE.

Alla fine, ascolterà il notifyDataSetChanged()cercapersone su visualizzazione.


Penso che dovresti implementare correttamente getItemPosition E restituire POSITION_NONE se il frammento non viene trovato.
tkhduracell,

46

La risposta data da alvarolb è sicuramente il modo migliore per farlo. Sulla base della sua risposta, un modo semplice per implementarlo è semplicemente archiviare le viste attive per posizione:

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;
}

Quindi una volta sovrascrivendo il notifyDataSetChangedmetodo è possibile aggiornare le visualizzazioni ...

@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();
}

Puoi effettivamente utilizzare un codice simile in instantiateIteme notifyDataSetChangedper aggiornare la tua vista. Nel mio codice uso esattamente lo stesso metodo.


8
puoi fornire un esempio di adattatore completo? viene fatto usando frammenti o no?

9
<aggiorna vista con nuovi dati> ?? qual è la media o dobbiamo aggiungere lui visualizzare qui o niente?
Akarsh M

Questo metodo aggiorna tutte le viste. Se desideri aggiornare solo una vista, puoi scrivere il tuo metodo notificationDataItemChanged in questo modo: public void notificationDataItemChanged (int pos) {TextView tv = (TextView) views.get (pos) .findViewById (R.id.tv); tv.setText (list.get (pos)); super.notifyDataSetChanged (); }
Kai Wang

Questo codice si arresta in modo
anomalo

@AntonDuzenko corretto, l'eliminazione delle visualizzazioni è piuttosto difficile.
MLProgrammer-CiM

28

Ho avuto lo stesso problema. Per me ha funzionato per estendere FragmentStatePagerAdapter e sovrascrivere i metodi seguenti:

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

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

}

Questa è l'unica soluzione che ha funzionato anche per me. Il mio caso non è l'aggiornamento del frammento, ma la rimozione o l'aggiunta dinamica di frammenti. Senza estendere FragmentStatePagerAdapter, ViewPager restituisce i frammenti memorizzati nella cache, posizione errata. Grazie!
Sira Lam,

Funziona bene con la libreria di supporto 28.0.0
Oleksii K.

Override per fare cosa? Come creare un oggetto stato associato a un adattatore?
Phani Rithvij,

Grazie compagno. Questa soluzione funziona solo
Parthi

20

Dopo ore di frustrazione, mentre provando tutte le soluzioni di cui sopra per superare questo problema e anche cercando molte soluzioni su altre questioni simili come questo , questo e questo , che tutti falliti con me per risolvere questo problema e per rendere il ViewPagerdistruggere il vecchio Fragmente riempire il pagercon la nuova Fragments. Ho risolto il problema come segue:

1) Rendi la ViewPagerclasse estesa FragmentPagerAdaptercome segue:

 public class myPagerAdapter extends FragmentPagerAdapter {

2) Creare un articolo per il ViewPagernegozio che memorizza il titlee il fragmentseguente:

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) Fai in modo che il costruttore del ViewPagertake my FragmentManageristanza lo memorizzi nel mio classcome segue:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

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

4) Creare un metodo per reimpostare i adapterdati con i nuovi dati eliminando direttamente tutti i precedenti fragmentdallo fragmentManagerstesso per fare in modo adapterche il nuovo set fragmentdal nuovo elenco sia il seguente:

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) Dal contenitore Activityo Fragmentnon inizializzare nuovamente l'adattatore con i nuovi dati. Impostare i nuovi dati tramite il metodo setPagerItemscon i nuovi dati come segue:

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();

Spero possa essere d'aiuto.


@Tomer qual è stato il problema che hai dovuto affrontare?
Sami Eltamawy,


9

Ho avuto lo stesso problema e la mia soluzione sta usando FragmentPagerAdaptercon l'override di FragmentPagerAdapter#getItemId(int position):

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

Per impostazione predefinita, questo metodo restituisce la posizione dell'articolo. Suppongo che ViewPagercontrolla se è itemIdstato modificato e ricrea la pagina solo se lo fosse. Ma la versione non sostituita restituisce la stessa posizione itemIdanche se la pagina è effettivamente diversa e ViewPager non definisce che la pagina viene sostituita e deve essere ricreata.

Per usare questo, long idè necessario per ogni pagina. Normalmente ci si aspetta che sia unico, ma suggerisco, in questo caso, che dovrebbe essere diverso dal valore precedente per la stessa pagina. Quindi, è possibile utilizzare il contatore continuo in adattatore o numeri interi casuali (con ampia distribuzione) qui.

Penso che sia un modo più coerente piuttosto che usare i tag di vista citati come soluzione in questo argomento. Ma probabilmente non per tutti i casi.


1
È. L'ho verificato: solo POSITION_NONE non è abbastanza. Grazie per la tua meravigliosa risposta! :)
Slava,

Questa funzione non è nemmeno presente in PagerAdapter. Che classe è questa?
Saket,

@Saket, hai ragione, la lezione èFragmentPagerAdapter
atlascoder

6

Ho trovato una decisione molto interessante di questo problema. Invece di usare FragmentPagerAdapter , che mantiene in memoria tutti i frammenti, possiamo usare FragmentStatePagerAdapter ( android.support.v4.app.FragmentStatePagerAdapter ), che ricaricano il frammento ogni volta, quando lo selezioniamo.

Le realizzazioni di entrambi gli adattatori sono identiche. Pertanto, è sufficiente modificare " Estendi FragmentPagerAdapter " in " Estendi FragmentStatePagerAdapter "


buon punto, aiuterà gli altri a risolvere i problemi di memoria di viewPager relativi al frammento
Ashu Kumar,

Ottima risposta, questo mi ha aiutato molto.
Sergio Marani,

5

Dopo aver cercato molto questo problema, ho trovato un'ottima soluzione che penso sia il modo giusto di procedere. In sostanza, istanzaItem viene chiamato solo quando la vista viene istanziata e mai più a meno che la vista non venga distrutta (questo è ciò che accade quando si sovrascrive la funzione getItemPosition per restituire POSITION_NONE). Invece, quello che vuoi fare è salvare le viste create e aggiornarle nell'adattatore, generare una funzione get in modo che qualcun altro possa aggiornarla o una funzione set che aggiorni l'adattatore (il mio preferito).

Quindi, nel tuo MyViewPagerAdapter aggiungi una variabile come:

private View updatableView;

un elemento nell'istanza:

 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;
    }

quindi, in questo modo, puoi creare una funzione che aggiornerà la tua vista:

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

Spero che sia di aiuto!


Mi sono reso conto che non stavo impostando i dati instantiateItem, ecco perché le mie visualizzazioni non si stavano aggiornando. Dalla tua risposta me ne sono reso conto. +1
Phani Rithvij,

5

Due anni e mezzo dopo che il PO ha posto la sua domanda, questo problema è ancora, beh, ancora un problema. È ovvio che la priorità di Google su questo non è particolarmente alta, quindi piuttosto che trovare una soluzione, ho trovato una soluzione alternativa. La grande svolta per me è stata scoprire quale fosse la vera causa del problema (vedere la risposta accettata in questo post ). Una volta evidente che il problema era che le pagine attive non venivano aggiornate correttamente, la mia soluzione era ovvia:

Nel mio frammento (le pagine):

  • Ho preso tutto il codice che popola il modulo da onCreateView e l'ho messo in una funzione chiamata PopulateForm che può essere chiamata da qualsiasi luogo, piuttosto che dal framework. Questa funzione tenta di ottenere la vista corrente utilizzando getView e, se è null, restituisce semplicemente. È importante che PopulateForm contenga solo il codice visualizzato: tutto l'altro codice che crea listener FocusChange e simili è ancora in OnCreate
  • Crea un valore booleano che può essere utilizzato come flag che indica che il modulo deve essere ricaricato. Il mio è mbReloadForm
  • Sostituisci OnResume () per chiamare PopulateForm () se è impostato mbReloadForm.

Nella mia attività, dove eseguo il caricamento delle pagine:

  • Vai a pagina 0 prima di cambiare qualsiasi cosa. Sto usando FragmentStatePagerAdapter, quindi so che due o tre pagine sono interessate al massimo. Passare alla pagina 0 assicura che io abbia sempre e solo il problema alle pagine 0, 1 e 2.
  • Prima di cancellare il vecchio elenco, prendi la sua dimensione (). In questo modo sai quante pagine sono interessate dal bug. Se> 3, riducilo a 3 - se stai usando un PagerAdapter diverso, dovrai vedere quante pagine devi gestire (forse tutte?)
  • Ricarica i dati e chiama pageAdapter.notifyDataSetChanged ()
  • Ora, per ciascuna delle pagine interessate, vedi se la pagina è attiva usando pager.getChildAt (i) - questo ti dice se hai una vista. In tal caso, chiama pager.PopulateView (). In caso contrario, impostare il flag ReloadForm.

Dopodiché, quando ricarichi una seconda serie di pagine, il bug causerà comunque la visualizzazione dei vecchi dati. Tuttavia, ora verranno aggiornati e vedrai i nuovi dati: i tuoi utenti non sapranno che la pagina non è mai stata errata perché questo aggiornamento avverrà prima che vedano la pagina.

Spero che questo aiuti qualcuno!


5

Tutte queste soluzioni non mi hanno aiutato. così ho trovato una soluzione funzionante: puoi setAdaptersempre, ma non è abbastanza. è necessario eseguire queste operazioni prima di cambiare l'adattatore:

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

e dopo questo:

viewPager.setAdapter(adapter);

Grazie! Nel mio caso ho usato ViewPagerframmenti interni, quindi sostituiti slideShowPagerAdapter.getFragmentManager()con getChildFragmentManager(). Forse getFragmentManager()ti aiuterà nel tuo caso. Ho usato FragmentPagerAdapter, non è FragmentStatePagerAdapter. Vedi anche stackoverflow.com/a/25994654/2914140 per un modo confuso.
CoolMind,

5

Un modo molto più semplice: utilizzare a FragmentPagerAdaptere avvolgere le viste impaginate su frammenti. Vengono aggiornati


5

Grazie, rui.araujo e Alvaro Luis Bustamante. All'inizio, provo ad usare il modo di rui.araujo, perché è facile. Funziona ma quando i dati cambiano, la pagina verrà ridisegnata ovviamente. È un male, quindi provo ad usare il modo di Alvaro Luis Bustamante. È perfetto. Ecco il codice:

@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;
    }
}

E quando i dati cambiano:

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();
    }
}

4

Nel caso in cui qualcuno stia usando l' adattatore basato su FragmentStatePagerAdapter (che consentirà a ViewPager di creare pagine minime necessarie per scopi di visualizzazione, al massimo 2 per il mio caso), la risposta di @ rui.araujo di sovrascrivere getItemPosition nell'adattatore non causerà sprechi significativi, ma continua a può essere migliorato.

In pseudo codice:

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;
}

all'interno di ViewPager, era abbastanza facile conoscere la posizione passata dell'elemento, l'implementazione più perfetta è semplicemente restituire la nuova posizione getItemPosition()quando il set di dati è cambiato e ViewPager saprà che sono cambiati o meno. purtroppo ViewPager non lo ha fatto.
VinceStyling,

3

Ho avuto un problema simile in cui avevo quattro pagine e una delle pagine ha aggiornato le viste sulle altre tre. Sono stato in grado di aggiornare i widget (SeekBars, TextViews, ecc.) Sulla pagina adiacente alla pagina corrente. Le ultime due pagine avrebbero widget non inizializzati durante la chiamata mTabsAdapter.getItem(position).

Per risolvere il mio problema, ho usato setSelectedPage(index)prima di chiamare getItem(position). Ciò creerebbe un'istanza della pagina, permettendomi di essere in grado di modificare valori e widget su ogni pagina.

Dopo tutto l'aggiornamento vorrei usare setSelectedPage(position)seguito da notifyDataSetChanged().

Puoi vedere un leggero sfarfallio in ListView nella pagina di aggiornamento principale, ma nulla di evidente. Non l'ho provato fino in fondo, ma risolve il mio problema immediato.


Ciao, sono interessato al tuo codice, sto affrontando un problema simile, sono in grado di aggiornare i frammenti adiacenti ma il frammento attuale che è visibile non viene aggiornato. Sono molto confuso con tutte le risposte qui. come posso aggiornare le viste nel frammento corrente mostrato dall'adattatore VIewPager
Pankaj Nimgade,

3

Sto solo pubblicando questa risposta nel caso in cui qualcun altro la ritenga utile. Per fare esattamente la stessa cosa, ho semplicemente preso il codice sorgente di ViewPager e PagerAdapter dalla libreria di compatibilità e l'ho compilato nel mio codice (devi risolvere tutti gli errori e importare te stesso, ma sicuramente può essere fatto).

Quindi, in CustomViewPager, creare un metodo chiamato updateViewAt (int position). La vista stessa può essere ottenuta da mItems di ArrayList definiti nella classe ViewPager (è necessario impostare un ID per le viste all'elemento istante e confrontare questo id con la posizione nel metodo updateViewAt ()). Quindi è possibile aggiornare la vista, se necessario.


2

Immagino di avere le logiche di ViewPager.

Se devo aggiornare un set di pagine e visualizzarle in base a un nuovo set di dati, chiamo notifyDataSetChanged () . Quindi, ViewPager effettua un numero di chiamate a getItemPosition () , passando lì frammento come oggetto. Questo frammento può provenire da un vecchio set di dati (che voglio scartare) o da uno nuovo (che voglio visualizzare). Quindi, sovrascrivo getItemPosition () e lì devo determinare in qualche modo se il mio frammento proviene dal vecchio set di dati o da quello nuovo.

Nel mio caso ho un layout a 2 riquadri con un elenco degli elementi principali nel riquadro sinistro e una vista di scorrimento (ViewPager) a destra. Quindi, memorizzo un collegamento al mio oggetto principale corrente all'interno di PagerAdapter e anche all'interno di ogni frammento di pagina istanziato. Quando l'elemento principale selezionato nell'elenco cambia, memorizzo il nuovo elemento principale in PagerAdapter e chiamo notifyDataSetChanged () . E in getItemPosition sovrascritto () confronto l'oggetto principale dal mio adattatore con l'elemento superiore del mio frammento. E solo se non sono uguali, restituisco POSITION_NONE. Quindi, PagerAdapter reinventa tutti i frammenti che hanno restituito POSITION_NONE.

NOTA. Memorizzare l'id dell'elemento principale anziché un riferimento potrebbe essere un'idea migliore.

Lo snippet di codice qui sotto è un po 'schematico ma l'ho adattato dal codice effettivamente funzionante.

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);
  }
}

Grazie per tutti i ricercatori precedenti!


2

Il codice qui sotto ha funzionato per me.

Creare una classe che estende la classe FragmentPagerAdapter come di seguito.

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;
}}

Quindi, all'interno di ogni frammento creato, crea un metodo updateFragment. In questo metodo cambi le cose che devi cambiare nel frammento. Ad esempio, nel mio caso, Fragment0 conteneva un GLSurfaceView che visualizza un oggetto 3d basato su un percorso di un file .ply, quindi all'interno del mio metodo updateFragment cambio il percorso di questo file ply.

quindi creare un'istanza di ViewPager,

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

e un'istanza di Adpater,

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

allora fai questo,

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

Quindi all'interno della classe hai inizializzato la classe Adapter sopra e creato un viewPager, ogni volta che vuoi aggiornare uno dei tuoi frammenti (nel nostro caso Fragment0) usa il seguente:

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.

Questa soluzione si basava sulla tecnica suggerita da Alvaro Luis Bustamante.


1

1.Prima devi impostare il metodo getItemposition nella tua classe Pageradapter 2. Devi leggere la posizione esatta del View Pager 3. quindi inviare quella posizione come posizione dei dati del tuo nuovo 4. Scrivere il pulsante di aggiornamento sul listener click all'interno del setonPageChange ascoltatore

quel codice di programma è un po 'modificato per impostare solo il particolare elemento di posizione

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

1

quello che ha funzionato per me stava andando viewPager.getAdapter().notifyDataSetChanged();

e nell'adattatore inserendo il codice per aggiornare la vista in questo getItemPositionmodo

@Override
public int getItemPosition(Object object) {

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

    return super.getItemPosition(object);
}

potrebbe non essere il modo più corretto di farlo ma ha funzionato (il return POSITION_NONEtrucco ha causato un crash per me, quindi non era un'opzione)


1

Puoi aggiornare dinamicamente tutti i frammenti, puoi vedere in tre passaggi.

Nel tuo adattatore:

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;
}}

Ora nella tua attività:

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 nel tuo frammento, qualcosa del genere:

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();

}}

Puoi vedere il codice completo qui .

Grazie Alvaro Luis Bustamante.


1

Sempre di ritorno POSITION_NONE è un modo semplice ma un po 'inefficiente perché evoca l'istanza di tutte le pagine che sono già state istanziate.

Ho creato una libreria ArrayPagerAdapter per modificare dinamicamente gli elementi in PagerAdapters.

Internamente, gli adattatori di questa libreria ritornano POSITION_NONEsugetItemPosiition() solo quando è necessario.

Puoi modificare gli elementi in modo dinamico come segue usando questa libreria.

@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 libreria Thils supporta anche le pagine create da Fragments.


1

Questo è per tutti quelli come me, che hanno bisogno di aggiornare Viewpager da un servizio (o altro thread in background) e nessuna delle proposte ha funzionato: dopo un po 'di logcheck ho capito, che il metodo notifyDataSetChanged () non ritorna mai. getItemPosition (oggetto Object) viene chiamato all end lì senza ulteriore elaborazione. Poi ho trovato nei documenti della classe PagerAdapter padre (non nei documenti delle sottoclassi), "Le modifiche al set di dati devono avvenire sul thread principale e devono terminare con una chiamata a notificationDataSetChanged ()". Quindi, la soluzione di lavoro in questo caso era (usando FragmentStatePagerAdapter e getItemPosition (oggetto Object) impostato per restituire POSITION_NONE):

e quindi la chiamata a notificationDataSetChanged ():

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

1

È possibile aggiungere la trasformazione del pager su Viewpager in questo modo

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

Nel codice seguente ho cambiato il mio colore di visualizzazione in fase di esecuzione durante lo scorrimento del cercapersone

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);
     }
   }
}

1

So che sono in ritardo ma può comunque aiutare qualcuno. Sto solo estendendo la risposta accetping e ho anche aggiunto il commento su di essa.

bene,

la risposta stessa dice che è inefficiente

quindi per poterlo aggiornare solo quando necessario puoi farlo

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);
    }
}

1

ViewPager non è stato progettato per supportare la modifica dinamica della vista.

Ne ho avuto conferma mentre cercavo un altro bug relativo a questo https://issuetracker.google.com/issues/36956111 e in particolare https://issuetracker.google.com/issues/36956111#comment56

Questa domanda è un po 'vecchia, ma Google ha recentemente risolto questo problema con ViewPager2 . Permetterà di sostituire soluzioni fatte a mano (non mantenute e potenzialmente difettose) con soluzioni standard. Impedisce inoltre di ricreare inutilmente le visualizzazioni come fanno alcune risposte.

Per gli esempi di ViewPager2, puoi consultare https://github.com/googlesamples/android-viewpager2

Se si desidera utilizzare ViewPager2, sarà necessario aggiungere la seguente dipendenza nel file build.gradle:

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

Quindi è possibile sostituire ViewPager nel file XML con:

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

Successivamente, dovrai sostituire ViewPager con ViewPager2 nella tua attività

ViewPager2 necessita di un RecyclerView.Adapter o di un FragmentStateAdapter, nel tuo caso può essere 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);
        }
    }
} 

Nel caso in cui si stesse utilizzando un TabLayout, è possibile utilizzare 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();

Quindi sarai in grado di aggiornare le tue viste modificando i dati dell'adattatore e chiamando il metodo notifyDataSetChanged



0

Penso di aver fatto un modo semplice per notificare le modifiche al set di dati:

Innanzitutto, modifica un po 'il modo in cui funziona la funzione 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;
    }

per "updateView", riempi la vista con tutti i dati che desideri inserire (setText, setBitmapImage, ...).

verifica che destroyView funzioni in questo modo:

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

Supponiamo ora di dover modificare i dati, farlo, quindi chiamare la funzione successiva su 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
}

Ad esempio, se si desidera notificare a tutte le visualizzazioni mostrate dal viewPager che qualcosa è cambiato, è possibile chiamare:

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

Questo è tutto.


0

Per quello che vale, su KitKat + sembra che adapter.notifyDataSetChanged()sia sufficiente a far apparire le nuove viste, a condizione che tu sia setOffscreenPageLimitsufficientemente alto. Sono in grado di ottenere il comportamento desiderato facendo viewPager.setOffscreenPageLimit(2).


0

Io in realtà uso notifyDataSetChanged()su ViewPagere CirclePageIndicatordopo che io chiamo destroyDrawingCache()su ViewPagere funziona .. Nessuna delle altre soluzioni lavorato per me.


La domanda originale ha quasi 2 anni, sono sicuro che ci sono molte differenze nell'API Android ormai che le risposte qui potrebbero non essere adatte a tutte le versioni dell'API Android.
C0de Attacco
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.