Aggiorna i dati in ListFragment come parte di ViewPager


142

Sto usando il ViewPager di compatibilità v4 in Android. My FragmentActivity ha un sacco di dati che devono essere visualizzati in modi diversi su pagine diverse nel mio ViewPager. Finora ho solo 3 istanze dello stesso ListFragment, ma in futuro avrò 3 istanze di ListFragment differenti. ViewPager si trova su uno schermo del telefono verticale, gli elenchi non sono affiancati.

Ora un pulsante su ListFragment avvia un'attività a tutta pagina separata (tramite FragmentActivity), che restituisce e FragmentActivity modifica i dati, li salva, quindi tenta di aggiornare tutte le viste nel suo ViewPager. È qui, dove sono bloccato.

public class ProgressMainActivity extends FragmentActivity
{
    MyAdapter mAdapter;
    ViewPager mPager;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    ...
        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager) findViewById(R.id.viewpager);
        mPager.setAdapter(mAdapter);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        ...
        updateFragments();
        ...
    }
    public void updateFragments()
    {
        //Attempt 1:
        //mAdapter.notifyDataSetChanged();
        //mPager.setAdapter(mAdapter);

        //Attempt 2:
        //HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentById(mAdapter.fragId[0]);
        //fragment.updateDisplay();
    }

    public static class MyAdapter extends FragmentPagerAdapter implements
         TitleProvider
    {
      int[] fragId = {0,0,0,0,0};
      public MyAdapter(FragmentManager fm)
      {
         super(fm);
      }
      @Override
      public String getTitle(int position){
         return titles[position];
      }
      @Override
      public int getCount(){
         return titles.length;
      }

      @Override
      public Fragment getItem(int position)
      {

         Fragment frag = HomeListFragment.newInstance(position);
         //Attempt 2:
         //fragId[position] = frag.getId();
         return frag;
      }

      @Override
      public int getItemPosition(Object object) {
         return POSITION_NONE; //To make notifyDataSetChanged() do something
     }
   }

    public class HomeListFragment extends ListFragment
    {
    ...
        public static HomeListFragment newInstance(int num)
        {
            HomeListFragment f = new HomeListFragment();
            ...
            return f;
        }
   ...

Ora, come puoi vedere, il mio primo tentativo è stato quello di notificareDataSetChanged sull'intero FragmentPagerAdapter, e questo ha mostrato di aggiornare i dati a volte, ma altri ho ottenuto IllegalStateException: Impossibile eseguire questa azione dopo onSaveInstanceState.

Il mio secondo tentativo ha comportato il tentativo di chiamare una funzione di aggiornamento nel mio ListFragment, ma getId in getItem ha restituito 0. Secondo i documenti che ho provato da

acquisizione di un riferimento al frammento da FragmentManager, utilizzando findFragmentById () o findFragmentByTag ()

ma non conosco il tag o l'id dei miei frammenti! Ho un android: id = "@ + id / viewpager" per ViewPager e un android: id = "@ android: id / list" per il mio ListView nel layout ListFragment, ma non penso che siano utili.

Quindi, come posso: a) aggiornare l'intero ViewPager in modo sicuro in una sola volta (idealmente riportando l'utente alla pagina in cui si trovava prima) - è ok che l'utente veda la vista cambiare. O preferibilmente, b) chiamare una funzione in ciascun ListFragment interessato per aggiornare ListView manualmente.

Qualsiasi aiuto sarebbe accettato con gratitudine!

Risposte:


58

Prova a registrare il tag ogni volta che viene creata un'istanza di Fragement.

public class MPagerAdapter extends FragmentPagerAdapter {
    private Map<Integer, String> mFragmentTags;
    private FragmentManager mFragmentManager;

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

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

    @Override
    public Fragment getItem(int position) {
        return Fragment.instantiate(mContext, AFragment.class.getName(), null);
    }

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

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

Questo sembra OK ma non funziona nella mia classe. Funziona con te? Ricevo ancora nullframmento.
sandalone,

1
Funziona per me, l'ho usato in diversi progetti. Puoi provare a leggere il codice sorgente di FragmentPagerAdapter e stampare alcuni log per eseguire il debug.
Faylon,

Grazie funziona come un incantesimo anche dopo aver ruotato gli schermi. Anche io sono stato in grado di cambiare il contenuto di FragmentA usando MyActivity da FragmentB, usando questa soluzione.
Mehdi Fanai,

5
Funziona per FragmentPagerAdaperma fallisce FragmentStatePagerAdaper( Fragment.getTag()sempre ritorno null.
Motore Bai

1
Ho provato questa soluzione, ho creato un metodo per aggiornare i dati del frammento. Ma ora non riesco a vedere le opinioni del frammento. Vedo che i dati si frammenteranno ma le viste non sono visibili. Qualsiasi aiuto?
Arun Badole,

256

La risposta di Barkside funziona con FragmentPagerAdapterma non funziona FragmentStatePagerAdapter, perché non imposta tag su frammenti a cui passa FragmentManager.

Con FragmentStatePagerAdaptersembra che possiamo cavarcela, usando la sua instantiateItem(ViewGroup container, int position)chiamata. Restituisce riferimento al frammento in posizione position. Se FragmentStatePagerAdaptercontiene già un riferimento al frammento in questione, instantiateItemrestituisce semplicemente il riferimento a quel frammento e non chiama getItem()per istanziarlo di nuovo.

Quindi, supponiamo, attualmente sto guardando il frammento # 50 e voglio accedere al frammento # 49. Dato che sono vicini, ci sono buone probabilità che il numero 49 sia già istanziato. Così,

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)

5
Semplificherei semplicemente trasformando "a" in PagerAdapter (PagerAdapter a = pager.getAdapter ();). O anche MyFragment f49 = (MyFragment) pager.getAdapter (). InstantiateItem (pager, 49); instantiateItem () proviene da PagerAdapter, quindi non è necessario digitarlo per chiamare instantiateItem ()
Dandre Allison

12
... e nel caso qualcuno si stia chiedendo, ViewPager tiene traccia dell'indice del frammento visualizzato (ViewPager.getCurrentItem ()), che è il 49 nell'esempio sopra ... ma devo dire che Sono stupito che l'API ViewPager non restituisca direttamente un riferimento al frammento reale.
mblackwell8

6
Con FragmentStatePagerAdapter, usando il codice sopra con a.instantiateItem(viewPager, viewPager.getCurrentItem());(per sbarazzarsi del 49) come proposto da @ mblackwell8 funziona perfettamente.
Cedric Simon

1
Aspetta, quindi devo passare ViewPager a ogni frammento che contiene solo per poter fare cose alle viste nel frammento? Attualmente tutto ciò che faccio in onCreate nella pagina corrente accade nella pagina successiva. Questo sembra estremamente losco per quanto riguarda le perdite.
G_V,

è importante ricordare che qualsiasi chiamata a instantiateItemdeve essere circondata da startUpdate/ finishUpdatechiamate. i dettagli sono nella mia risposta a una domanda simile: stackoverflow.com/questions/14035090/...
morgwai

154

OK, penso di aver trovato un modo per eseguire la richiesta b) nella mia domanda, quindi condividerò a beneficio degli altri. Il tag di frammenti all'interno di un ViewPager è nel modulo "android:switcher:VIEWPAGER_ID:INDEX", dove si VIEWPAGER_IDtrova il R.id.viewpagerlayout in XML, e INDEX è la posizione nel viewpager. Quindi se la posizione è nota (es. 0), posso eseguire in updateFragments():

      HomeListFragment fragment = 
          (HomeListFragment) getSupportFragmentManager().findFragmentByTag(
                       "android:switcher:"+R.id.viewpager+":0");
      if(fragment != null)  // could be null if not instantiated yet
      {
         if(fragment.getView() != null) 
         {
            // no need to call if fragment's onDestroyView() 
            //has since been called.
            fragment.updateDisplay(); // do what updates are required
         }
      }

Non ho idea se questo è un modo valido per farlo, ma lo farà fino a quando non verrà suggerito qualcosa di meglio.


4
Ho effettuato l'accesso solo per +1 alla tua risposta e domanda.
SuitUp

5
dal momento che questo non è nella documentazione ufficiale, non lo userei. Cosa succede se cambiano il tag in una versione futura?
Thierry-Dimitri Roy,

4
FragmentPagerAdapter viene fornito con un metodo statico makeFragmentNameutilizzato per generare il tag che è possibile utilizzare invece per un approccio leggermente meno HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
caotico

8
@dgmltn, makeFragmentNameè un private staticmetodo, quindi non è possibile accedervi.
StenaviN,

1
Dolce madre di Gesù, funziona! Terrò solo le dita incrociate e lo userò.
Bogdan Alexandru,

35

Se me lo chiedi, la seconda soluzione nella pagina seguente, tenendo traccia di tutte le pagine di frammenti "attivi", è migliore: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

La risposta di Barkside è troppo confusa per me.

tieni traccia di tutte le pagine di frammenti "attive". In questo caso, si tiene traccia delle pagine dei frammenti in FragmentStatePagerAdapter, utilizzato da ViewPager.

private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();

public Fragment getItem(int index) {
    Fragment myFragment = MyFragment.newInstance();
    mPageReferences.put(index, myFragment);
    return myFragment;
}

Per evitare di mantenere un riferimento a pagine di frammenti "inattive", è necessario implementare il metodo destroyItem (...) di FragmentStatePagerAdapter:

public void destroyItem(View container, int position, Object object) {
    super.destroyItem(container, position, object);
    mPageReferences.remove(position);
}

... e quando è necessario accedere alla pagina attualmente visibile, si chiama:

int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);

... dove il metodo getFragment (int) di MyAdapter è simile al seguente:

public MyFragment getFragment(int key) {
    return mPageReferences.get(key);
}

"


@Frank seconda soluzione nel link è semplice ...! Grazie .. :)
TheFlash

3
Meglio se usi uno SparseArray invece di una mappa
GaRRaPeTa


ho aggiornato la mia risposta in modo che utilizzi SparseArray, se scegli come target <11, usa invece SparseArrayCompat perché la classe è stata aggiornata nella versione 11.
Frank

2
Ciò si interromperà negli eventi del ciclo di vita. Vedi stackoverflow.com/a/24662049/236743
Dori

13

Ok, dopo aver testato il metodo di @barkside sopra, non sono riuscito a farlo funzionare con la mia applicazione. Poi mi sono ricordato che anche l' app IOSched2012 utilizza una viewpager, ed è qui che ho trovato la mia soluzione. Non utilizza alcun ID frammento o tag poiché questi non sono memorizzati daviewpager in un modo facilmente accessibile.

Ecco le parti importanti delle app IOSched HomeActivity. Presta particolare attenzione al commento , poiché in questo sta la chiave .:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // Since the pager fragments don't have known tags or IDs, the only way to persist the
    // reference is to use putFragment/getFragment. Remember, we're not persisting the exact
    // Fragment instance. This mechanism simply gives us a way to persist access to the
    // 'current' fragment instance for the given fragment (which changes across orientation
    // changes).
    //
    // The outcome of all this is that the "Refresh" menu button refreshes the stream across
    // orientation changes.
    if (mSocialStreamFragment != null) {
        getSupportFragmentManager().putFragment(outState, "stream_fragment",
                mSocialStreamFragment);
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    if (mSocialStreamFragment == null) {
        mSocialStreamFragment = (SocialStreamFragment) getSupportFragmentManager()
                .getFragment(savedInstanceState, "stream_fragment");
    }
}

E memorizza le tue istanze Fragmentsin questo FragmentPagerAdaptermodo:

    private class HomePagerAdapter extends FragmentPagerAdapter {
    public HomePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return (mMyScheduleFragment = new MyScheduleFragment());

            case 1:
                return (mExploreFragment = new ExploreFragment());

            case 2:
                return (mSocialStreamFragment = new SocialStreamFragment());
        }
        return null;
    }

Inoltre, ricorda di proteggere le tue Fragmentchiamate in questo modo:

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }

Ryan, questo per dire che stai esagerando con le funzioni che ViewPager utilizza per "mantenere" le tue istanze di frammento e impostarle su una variabile pubblica accessibile? Perché sull'istanza di ripristino hai if (mSocialStreamFragment == null) {?
Thomas Clowes,

1
questa è una buona risposta e +1 per il riferimento dell'app io12 - non so se funzionerà attraverso le modifiche del ciclo di vita, anche se a causa della getItem()mancata chiamata dopo che il genitore è stato ricreato a causa di una modifica del ciclo di vita. Vedi stackoverflow.com/a/24662049/236743 . Nell'esempio questo è parzialmente protetto da un frammento ma non dagli altri due
Dori,

2

È possibile copiare FragmentPagerAdaptere modificare del codice sorgente, aggiungeregetTag() metodo

per esempio

public abstract class AppFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;

private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;

public AppFragmentPagerAdapter(FragmentManager fm) {
    mFragmentManager = fm;
}


public abstract Fragment getItem(int position);

@Override
public void startUpdate(ViewGroup container) {
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    final long itemId = getItemId(position);


    String name = getTag(position);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);

        mCurTransaction.add(container.getId(), fragment,
                getTag(position));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment) object).getView());
    mCurTransaction.detach((Fragment) object);
}

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            mCurrentPrimaryItem.setUserVisibleHint(false);
        }
        if (fragment != null) {
            fragment.setMenuVisibility(true);
            fragment.setUserVisibleHint(true);
        }
        mCurrentPrimaryItem = fragment;
    }
}

@Override
public void finishUpdate(ViewGroup container) {
    if (mCurTransaction != null) {
        mCurTransaction.commitAllowingStateLoss();
        mCurTransaction = null;
        mFragmentManager.executePendingTransactions();
    }
}

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

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

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


public long getItemId(int position) {
    return position;
}

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

protected abstract String getTag(int position);
}

quindi estenderlo, sovrascrivere questi metodi astratti, non è necessario aver paura del cambiamento del gruppo Android

FragmentPageAdapter codice sorgente in futuro

 class TimeLinePagerAdapter extends AppFragmentPagerAdapter {


    List<Fragment> list = new ArrayList<Fragment>();


    public TimeLinePagerAdapter(FragmentManager fm) {
        super(fm);
        list.add(new FriendsTimeLineFragment());
        list.add(new MentionsTimeLineFragment());
        list.add(new CommentsTimeLineFragment());
    }


    public Fragment getItem(int position) {
        return list.get(position);
    }

    @Override
    protected String getTag(int position) {
        List<String> tagList = new ArrayList<String>();
        tagList.add(FriendsTimeLineFragment.class.getName());
        tagList.add(MentionsTimeLineFragment.class.getName());
        tagList.add(CommentsTimeLineFragment.class.getName());
        return tagList.get(position);
    }


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


}

1

Funziona anche senza problemi:

da qualche parte nel layout del frammento di pagina:

<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="@+id/fragment_reference">
     <View android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/>
</FrameLayout>

nel frammento di onCreateView ():

...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;

e metodo per restituire il frammento da ViewPager, se esiste:

public Fragment getFragment(int pageIndex) {        
        View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
        if (w == null) return null;
        View r = (View) w.getParent();
        return (Fragment) r.getTag();
}

1

In alternativa puoi sovrascrivere il setPrimaryItemmetodo in questo FragmentPagerAdaptermodo:

public void setPrimaryItem(ViewGroup container, int position, Object object) { 
    if (mCurrentFragment != object) {
        mCurrentFragment = (Fragment) object; //Keep reference to object
        ((MyInterface)mCurrentFragment).viewDidAppear();//Or call a method on the fragment
    }

    super.setPrimaryItem(container, position, object);
}

public Fragment getCurrentFragment(){
    return mCurrentFragment;
}

0

Voglio dare il mio approccio nel caso in cui possa aiutare chiunque altro:

Questo è il mio adattatore per cercapersone:

 public class CustomPagerAdapter extends FragmentStatePagerAdapter{
    private Fragment[] fragments;

    public CustomPagerAdapter(FragmentManager fm) {
        super(fm);
        fragments = new Fragment[]{
                new FragmentA(),
                new FragmentB()
        };
    }

    @Override
    public Fragment getItem(int arg0) {
        return fragments[arg0];
    }

    @Override
    public int getCount() {
        return fragments.length;
    }

}

Nella mia attività ho:

public class MainActivity {
        private ViewPager view_pager;
        private CustomPagerAdapter adapter;


        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        adapter = new CustomPagerAdapter(getSupportFragmentManager());
        view_pager = (ViewPager) findViewById(R.id.pager);
        view_pager.setAdapter(adapter);
        view_pager.setOnPageChangeListener(this);
          ...
         }

}

Quindi per ottenere il frammento attuale quello che faccio è:

int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);

1
questo si interromperà dopo i metodi del ciclo di vita delle attività / dei frammenti
Dori,

0

Questa è la mia soluzione poiché non ho bisogno di tenere traccia delle mie schede e devo aggiornarle tutte comunque.

    Bundle b = new Bundle();
    b.putInt(Constants.SharedPreferenceKeys.NUM_QUERY_DAYS,numQueryDays);
    for(android.support.v4.app.Fragment f:getSupportFragmentManager().getFragments()){
        if(f instanceof HomeTermFragment){
            ((HomeTermFragment) f).restartLoader(b);
        }
    }
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.