Risposte:
La risposta principale si basa su un nome generato dal framework. Se questo dovesse mai cambiare, allora non funzionerà più.
Che dire di questa soluzione, di priorità instantiateItem()e destroyItem()del tuo Fragment(State)PagerAdapter:
public class MyPagerAdapter extends FragmentStatePagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return ...;
}
@Override
public Fragment getItem(int position) {
return MyFragment.newInstance(...);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}
Questo sembra funzionare per me quando ho a che fare con frammenti disponibili. I frammenti che non sono stati ancora istanziati, restituiranno null quando si chiama getRegisteredFragment. Ma lo sto usando principalmente per Fragmentestrarre la corrente da ViewPager: adapater.getRegisteredFragment(viewPager.getCurrentItem())e questo non ritornerà null.
Non sono a conoscenza di altri svantaggi di questa soluzione. Se ce ne sono, mi piacerebbe saperlo.
MyPagerAdapterviene distrutto a causa del ciclo di vita (cioè ruota), non registerdFragmentsandrà perso? Il Activity/ Fragmentusing MyPagerAdapterdovrà salvarlo in onSaveInstanceState, e quindi dovrà aggiornarlo con il nuovo riferimento FragmentManager?
getItemnon viene più richiamato su rotate (per eventuali frags che sono già stati creati), poiché FragmentManagerripristina gli stati di Fragmentsesso contenuti nel cercapersone. Se instantiateItemviene chiamato quando ciascuno Fragmentviene ripristinato, questa soluzione è in realtà più sicura e più a prova di futuro della mia o della risposta accettata. Prenderò in considerazione di provare questo da solo.
Per estrarre frammenti da un ViewPager ci sono molte risposte qui e su altri thread / blog SO correlati. Tutti quelli che ho visto sono rotti e generalmente sembrano rientrare in uno dei due tipi elencati di seguito. Ci sono alcune altre soluzioni valide se vuoi solo afferrare il frammento corrente, come questa altra risposta su questo thread.
Se si utilizza FragmentPagerAdaptervedere di seguito. Se ne FragmentStatePagerAdaptervale la pena guardare questo . Afferrare gli indici che non sono quelli attuali in un FragmentStateAdapter non è così utile in quanto per loro natura questi saranno completamente abbattuti, fuori dalla vista / fuori dai limiti offScreenLimit.
Sbagliato: mantenere il proprio elenco interno di frammenti, aggiunto a quando FragmentPagerAdapter.getItem()viene chiamato
SparseArrayoMapgetItemviene chiamato solo la prima volta che una pagina viene spostata (o ottenuta se ViewPager.setOffscreenPageLimit(x)> 0) in ViewPager, se l'hosting Activity/ Fragmentviene ucciso o riavviato, l'interno SpaseArrayverrà cancellato quando viene ricreata la FragmentPagerActivity personalizzata, ma dietro le quinte il ViewPagers frammenti interni saranno ricreati, e getItemsaranno non essere chiamati per qualsiasi degli indici, così la possibilità di ottenere un frammento di indice verranno persi per sempre. Puoi renderlo conto salvando e ripristinando questi riferimenti a frammenti tramite FragmentManager.getFragment()e putFragmentma questo inizia a diventare IMHO disordinato.Sbagliato: costruisci il tuo tag id corrispondente a quello che viene usato sotto il cofanoFragmentPagerAdapter e usalo per recuperare i frammenti di pagina dalFragmentManager
ViewPagerquello che potrebbe cambiare in qualsiasi momento o per qualsiasi versione del sistema operativo.Il metodo ricreato per questa soluzione è
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
ViewPager.instantiateItem()Un approccio simile a quello getItem()sopra, ma non interruzione del ciclo di vita, è quello di agganciare instantiateItem()invece di getItem()come il primo verrà chiamato ogni volta che l'indice viene creato / accessibile. Vedi questa risposta
FragmentViewPagerCostruisci la tua FragmentViewPagerclasse dall'origine della più recente libreria di supporto e modifica il metodo utilizzato internamente per generare i tag di frammento. Puoi sostituirlo con il seguente. Questo ha il vantaggio di sapere che la creazione del tag non cambierà mai e di non fare affidamento su un metodo / API privato, che è sempre pericoloso.
/**
* @param containerViewId the ViewPager this adapter is being supplied to
* @param id pass in getItemId(position) as this is whats used internally in this class
* @return the tag used for this pages fragment
*/
public static String makeFragmentName(int containerViewId, long id) {
return "android:switcher:" + containerViewId + ":" + id;
}
Quindi, come dice il documento, quando si desidera afferrare un frammento utilizzato per un indice, chiamare semplicemente qualcosa come questo metodo (che è possibile inserire nella FragmentPagerAdaptersottoclasse personalizzata o)) tenendo presente che il risultato potrebbe essere nullo se getItem non è stato ancora richiesto quella pagina cioè non è stata ancora creata.
/**
* @return may return null if the fragment has not been instantiated yet for that position - this depends on if the fragment has been viewed
* yet OR is a sibling covered by {@link android.support.v4.view.ViewPager#setOffscreenPageLimit(int)}. Can use this to call methods on
* the current positions fragment.
*/
public @Nullable Fragment getFragmentForPosition(int position)
{
String tag = makeFragmentName(mViewPager.getId(), getItemId(position));
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
return fragment;
}
Questa è una soluzione semplice e risolve i problemi nelle altre due soluzioni presenti ovunque sul Web
instantiateItem()che è quando si desidera accedere a un frammento che non è stato ancora visitato dopo che si è verificato un evento del ciclo di vita e che è stato visitato prima dell'evento del ciclo di vita. Una piccola differenza che conosco, ma è quella che ha portato a un bug sottile nel mio programma, quindi mi sto guardando in questo.
getItemId(pos)dentroFragmentStatePagerAdapter
Aggiungi i seguenti metodi al tuo FragmentPagerAdapter:
public Fragment getActiveFragment(ViewPager container, int position) {
String name = makeFragmentName(container.getId(), position);
return mFragmentManager.findFragmentByTag(name);
}
private static String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
getActiveFragment (0) deve funzionare.
Ecco la soluzione implementata in ViewPager https://gist.github.com/jacek-marchwicki/d6320ba9a910c514424d . Se qualcosa fallisce vedrai un buon registro degli arresti anomali.
Un'altra soluzione semplice:
public class MyPagerAdapter extends FragmentPagerAdapter {
private Fragment mCurrentFragment;
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
//...
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (getCurrentFragment() != object) {
mCurrentFragment = ((Fragment) object);
}
super.setPrimaryItem(container, position, object);
}
}
setPrimaryItem()viene chiamato dopo ViewPager.OnPageChangeListener#onPageSelected() non posso usarlo :-(
So che questo ha alcune risposte, ma forse questo aiuterà qualcuno. Ho usato una soluzione relativamente semplice quando avevo bisogno di ottenere un Fragmentmio ViewPager. Nel tuo Activityo Fragmenttenendo premuto il ViewPager, puoi usare questo codice per scorrere tutto Fragmentciò che contiene.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = fragmentPagerAdapter.getItem(i);
if(viewPagerFragment != null) {
// Do something with your Fragment
// Check viewPagerFragment.isResumed() if you intend on interacting with any views.
}
}
Se conosci la tua posizione Fragmentin ViewPager, puoi semplicemente chiamare getItem(knownPosition).
Se non conosci la posizione del tuo Fragmentin ViewPager, puoi avere i tuoi figli Fragmentsimplementare un'interfaccia con un metodo simile getUniqueId()e usarlo per differenziarli. Oppure puoi scorrere tutto Fragmentse controllare il tipo di classe, ad esempioif(viewPagerFragment instanceof FragmentClassYouWant)
!!! MODIFICARE !!!
Ho scoperto che getItemviene chiamato da un solo FragmentPagerAdapterquando ognuno Fragmentdeve essere creato la prima volta , dopodiché, sembra che Fragmentsvengano riciclati usando il FragmentManager. In questo modo, molte implementazioni diFragmentPagerAdapter creare nuove Fragment s in getItem. Usando il mio metodo sopra, questo significa che creeremo nuove Fragments ogni volta che getItemviene chiamato mentre esaminiamo tutti gli elementi in FragmentPagerAdapter. A causa di ciò, ho trovato un approccio migliore, usando invece FragmentManagerper ottenere ciascuno Fragment(usando la risposta accettata). Questa è una soluzione più completa e ha funzionato bene per me.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
String name = makeFragmentName(mViewPager.getId(), i);
Fragment viewPagerFragment = getChildFragmentManager().findFragmentByTag(name);
// OR Fragment viewPagerFragment = getFragmentManager().findFragmentByTag(name);
if(viewPagerFragment != null) {
// Do something with your Fragment
if (viewPagerFragment.isResumed()) {
// Interact with any views/data that must be alive
}
else {
// Flag something for update later, when this viewPagerFragment
// returns to onResume
}
}
}
E avrai bisogno di questo metodo.
private static String makeFragmentName(int viewId, int position) {
return "android:switcher:" + viewId + ":" + position;
}
ViewPagerstesso imposta quei tag. Questa soluzione è fragile perché si basa sulla conoscenza della convenzione di denominazione "nascosta" di ViewPager. La risposta di Streets of Boston è una soluzione molto più completa, che ora uso nel mio progetto (anziché in questo approccio).
Nel mio caso, nessuna delle soluzioni di cui sopra ha funzionato.
Tuttavia, poiché sto utilizzando Child Fragment Manager in un frammento, è stato utilizzato quanto segue:
Fragment f = getChildFragmentManager().getFragments().get(viewPager.getCurrentItem());
Funzionerà solo se i tuoi frammenti in Manager corrispondono all'elemento viewpager.
Per ottenere l' attuale frammento visibile da ViewPager . Sto usando questa semplice affermazione e funziona benissimo.
public Fragment getFragmentFromViewpager()
{
return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem())));
}
L'ho gestito facendo prima un elenco di tutti i frammenti ( List<Fragment> fragments;) che stavo per usare, quindi li ho aggiunti al cercapersone per semplificare la gestione del frammento attualmente visualizzato.
Così:
@Override
onCreate(){
//initialise the list of fragments
fragments = new Vector<Fragment>();
//fill up the list with out fragments
fragments.add(Fragment.instantiate(this, MainFragment.class.getName()));
fragments.add(Fragment.instantiate(this, MenuFragment.class.getName()));
fragments.add(Fragment.instantiate(this, StoresFragment.class.getName()));
fragments.add(Fragment.instantiate(this, AboutFragment.class.getName()));
fragments.add(Fragment.instantiate(this, ContactFragment.class.getName()));
//Set up the pager
pager = (ViewPager)findViewById(R.id.pager);
pager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments));
pager.setOffscreenPageLimit(4);
}
quindi questo può essere chiamato:
public Fragment getFragment(ViewPager pager){
Fragment theFragment = fragments.get(pager.getCurrentItem());
return theFragment;
}
così ho potuto scriverlo in un'istruzione if che sarebbe stata eseguita solo se fosse sul frammento corretto
Fragment tempFragment = getFragment();
if(tempFragment == MyFragmentNo2.class){
MyFragmentNo2 theFrag = (MyFragmentNo2) tempFragment;
//then you can do whatever with the fragment
theFrag.costomFunction();
}
ma questo è solo il mio approccio di hack e slash ma ha funzionato per me, lo uso faccio modifiche rilevanti al mio frammento attualmente visualizzato quando viene premuto il pulsante Indietro.
Questo si basa sulla risposta di Steven sopra. Ciò restituirà l'istanza effettiva del frammento che è già associato all'attività padre.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = (Fragment) mViewPager.getAdapter().instantiateItem(mViewPager, i);
if(viewPagerFragment != null && viewPagerFragment.isAdded()) {
if (viewPagerFragment instanceof FragmentOne){
FragmentOne oneFragment = (FragmentOne) viewPagerFragment;
if (oneFragment != null){
oneFragment.update(); // your custom method
}
} else if (viewPagerFragment instanceof FragmentTwo){
FragmentTwo twoFragment = (FragmentTwo) viewPagerFragment;
if (twoFragment != null){
twoFragment.update(); // your custom method
}
}
}
}
Non sono riuscito a trovare un modo semplice e pulito per farlo. Tuttavia, il widget ViewPager è solo un altro ViewGroup, che ospita i tuoi frammenti. ViewPager ha questi frammenti come figli immediati. Quindi potresti semplicemente scorrere su di essi (usando .getChildCount () e .getChildAt ()), e vedere se l'istanza di frammento che stai cercando è attualmente caricata nel ViewPager e ottenere un riferimento ad esso. Ad esempio, è possibile utilizzare un campo ID univoco statico per distinguere i frammenti.
Nota che ViewPager potrebbe non aver caricato il frammento che stai cercando poiché è un contenitore di virtualizzazione come ListView.
FragmentPagerAdapter è la fabbrica dei frammenti. Per trovare un frammento basato sulla sua posizione se è ancora in memoria, usa questo:
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
Codice di esempio per API di supporto v4.
Non è necessario chiamare getItem()o altri metodi in una fase successiva per ottenere il riferimento di un Fragmenthost ospitato all'interno ViewPager. Se desideri aggiornare alcuni dati all'interno, Fragmentutilizza questo approccio: Aggiorna ViewPager in modo dinamico?
La chiave è quella di impostare nuovi dati all'interno Adapere chiamare quelli notifyDataSetChanged()che a loro volta chiameranno getItemPosition(), passandoti un tuo riferimento Fragmente dandoti la possibilità di aggiornarli. Tutti gli altri modi richiedono di fare riferimento a te stesso o ad altri hack che non sono una buona soluzione.
@Override
public int getItemPosition(Object object) {
if (object instanceof UpdateableFragment) {
((UpdateableFragment) object).update(xyzData);
}
//don't return POSITION_NONE, avoid fragment recreation.
return super.getItemPosition(object);
}
getItemPosition()è un metodo nel tuo adattatore. Non lo chiamerai direttamente. Hai un riferimento del tuo adattatore, così chiami adapter.notifyDataSetChanged()chi a sua volta chiamerà il getItemPosition()tuo adattatore passando il riferimento dei tuoi Fragment. Potete vedere una piena attuazione in azione qui stackoverflow.com/questions/19891828/...
Fragmentma come ottenere un riferimento è discutibile. Altre soluzioni ottengono riferimenti FragmentManagerdall'uso di una stringa simile "android:switcher:"+ido POSITION_NONEdalla restituzione getItemosition()causando la rigenerazione di tutti i frammenti.
Deve estendersi FragmentPagerAdapteralla classe dell'adattatore ViewPager.
Se si utilizza FragmentStatePagerAdapterallora non si sarà in grado di trovare il vostro Fragmentdal suoID
public static String makeFragmentName(int viewPagerId, int index) {
return "android:switcher:" + viewPagerId + ":" + index;
}
Come usare questo metodo: -
Fragment mFragment = ((FragmentActivity) getContext()).getSupportFragmentManager().findFragmentByTag(
AppMethodUtils.makeFragmentName(mViewPager.getId(), i)
);
InterestViewFragment newFragment = (InterestViewFragment) mFragment;
Ehi, ho risposto a questa domanda qui . Fondamentalmente, è necessario eseguire l'override
public Object instantiateItem (contenitore ViewGroup, posizione int)
metodo di FragmentStatePagerAdapter.
La soluzione migliore è utilizzare l'estensione che abbiamo creato in CodePath chiamato SmartFragmentStatePagerAdapter . Seguendo tale guida, ciò semplifica notevolmente il recupero di frammenti e il frammento attualmente selezionato da un ViewPager. Fa anche un lavoro migliore nella gestione della memoria dei frammenti incorporati nell'adattatore.
Il modo più semplice e più conciso. Se tutti i tuoi frammenti ViewPagersono di classi diverse, puoi recuperarli e distinguerli come segue:
public class MyActivity extends Activity
{
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment.getClass() == MyFragment.class) {
mMyFragment = (MyFragment) fragment;
}
}
}
Ho implementato questo facilmente con un approccio un po 'diverso.
Il mio metodo FragmentAdapter.getItem personalizzato non ha restituito MyFragment (), ma l'istanza di MyFragment creata nel costruttore di FragmentAdapter.
Nella mia attività ho quindi ottenuto il frammento dall'adattatore, verificare se si tratta dell'istanza di frammento necessario, quindi eseguire il cast e utilizzare i metodi necessari.
Crea un ID risorsa intero in /values/integers.xml
<integer name="page1">1</integer>
<integer name="page2">2</integer>
<integer name="page3">3</integer>
Quindi nella funzione getItem di PagerAdapter:
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = FragmentOne.newInstance();
mViewPager.setTag(R.integer.page1,fragment);
}
else if (position == 1) {
fragment = FragmentTwo.newInstance();
mViewPager.setTag(R.integer.page2,fragment);
} else if (position == 2) {
fragment = FragmentThree.newInstance();
mViewPager.setTag(R.integer.page3,fragment);
}
return fragment;
}
Quindi nell'attività scrivi questa funzione per ottenere il riferimento al frammento:
private Fragment getFragmentByPosition(int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = (Fragment) mViewPager.getTag(R.integer.page1);
break;
case 1:
fragment = (Fragment) mViewPager.getTag(R.integer.page2);
break;
case 2:
fragment = (Fragment) mViewPager.getTag(R.integer.page3);
break;
}
return fragment;
}
Ottieni il riferimento al frammento chiamando la funzione sopra e quindi esegui il cast sul frammento personalizzato:
Fragment fragment = getFragmentByPosition(position);
if (fragment != null) {
FragmentOne fragmentOne = (FragmentOne) fragment;
}
Modo semplice per scorrere i frammenti in Gestione frammenti. Trova viewpager, che ha l'argomento position position, inserito in PlaceholderFragment newInstance statico pubblico (int sectionNumber) .
public PlaceholderFragment getFragmentByPosition(Integer pos){
for(Fragment f:getChildFragmentManager().getFragments()){
if(f.getId()==R.id.viewpager && f.getArguments().getInt("SECTNUM") - 1 == pos) {
return (PlaceholderFragment) f;
}
}
return null;
}
Nel frammento
public int getArgument(){
return mPage;
{
public void update(){
}
In FragmentActivity
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for(Fragment f:fragments){
if((f instanceof PageFragment)&&(!f.isDetached())){
PageFragment pf = (PageFragment)f;
if(pf.getArgument()==pager.getCurrentItem())pf.update();
}
}
in TabLayout ci sono più schede per Frammento. puoi trovare il frammento per Tag usando l'indice del frammento.
Per es. l'indice per Fragment1 è 0, quindi nel findFragmentByTag()metodo passa il tag per Viewpager.after usando fragmentTransaction che puoi aggiungere, sostituisci il frammento.
String tag = "android:switcher:" + R.id.viewPager + ":" + 0;
Fragment1 f = (Fragment1) getSupportFragmentManager().findFragmentByTag(tag);
Ok per l'adattatore FragmentStatePagerAdapter ho finanziato una soluzione:
nel tuo frammentoAttività:
ActionBar mActionBar = getSupportActionBar();
mActionBar.addTab(mActionBar.newTab().setText("TAB1").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment1.class.getName())));
mActionBar.addTab(mActionBar.newTab().setText("TAB2").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment2.class.getName())));
mActionBar.addTab(mActionBar.newTab().setText("TAB3").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment3.class.getName())));
viewPager = (STViewPager) super.findViewById(R.id.viewpager);
mPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), mActionBar);
viewPager.setAdapter(this.mPagerAdapter);
e crea un metodo nella tua classe FragmentActivity - Quindi quel metodo ti dà accesso al tuo frammento, devi solo dargli la posizione del frammento che desideri:
public Fragment getActiveFragment(int position) {
String name = MyPagerAdapter.makeFragmentName(position);
return getSupportFragmentManager().findFragmentByTag(name);
}
nel tuo adattatore:
public class MyPagerAdapter extends FragmentStatePagerAdapter {
private final ActionBar actionBar;
private final FragmentManager fragmentManager;
public MyPagerAdapter(FragmentManager fragmentManager, com.actionbarsherlock.app.ActionBarActionBar mActionBar) {super(fragmentManager);
this.actionBar = mActionBar;
this.fragmentManager = fragmentManager;
}
@Override
public Fragment getItem(int position) {
getSupportFragmentManager().beginTransaction().add(mTchatDetailsFragment, makeFragmentName(position)).commit();
return (Fragment)this.actionBar.getTabAt(position);
}
@Override
public int getCount() {
return this.actionBar.getTabCount();
}
@Override
public CharSequence getPageTitle(int position) {
return this.actionBar.getTabAt(position).getText();
}
private static String makeFragmentName(int viewId, int index) {
return "android:fragment:" + index;
}
}
Fragment yourFragment = yourviewpageradapter.getItem(int index);
indexè il posto del frammento nell'adattatore come hai aggiunto fragment1prima, quindi riprendi il fragment1passaggio indexcome 0 e così via per il resto
Fragmentin aWeakReferenceper garantire di non impedire che un frammento distrutto venga raccolto? Sembra proprio la cosa giusta da fare ...