it-swarm.it

Aggiorna i dati in ListFragment come parte di ViewPager

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 credo 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!

140
barkside

Prova a registrare il tag ogni volta che viene istanziato un frammento.

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

La risposta di Barkside funziona con FragmentPagerAdapter ma non funziona con FragmentStatePagerAdapter, perché non imposta tag su frammenti che passa a FragmentManager.

Con FragmentStatePagerAdapter sembra che possiamo cavarcela, usando la sua chiamata instantiateItem(ViewGroup container, int position). Restituisce il riferimento al frammento nella posizione position. Se FragmentStatePagerAdapter contiene già un riferimento al frammento in questione, instantiateItem restituisce semplicemente il riferimento a quel frammento e non chiama getItem() per istanziarlo nuovamente.

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)
252
Pēteris Caune

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 formato "Android:switcher:VIEWPAGER_ID:INDEX", Dove VIEWPAGER_ID È il R.id.viewpager Nel layout XML e INDICE è 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.

152
barkside

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-parte-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", dobbiamo 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);
}

"

35
Frank

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

Ecco parti importanti dalle 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 Fragments nel FragmentPagerAdapter in questo modo:

    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 chiamate Fragment in questo modo:

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

Puoi copiare FragmentPagerAdapter e modificare un po 'di codice sorgente, aggiungere il metodo getTag()

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 di Android Cambio di gruppo

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


}
2
Jiang Qi

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
Hox

In alternativa puoi sovrascrivere il metodo setPrimaryItem da FragmentPagerAdapter in questo modo:

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;
}
1
Vlad Spreys

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);
0
kiduxa

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