J'utilise le ViewPager de compatibilité v4 sous Android. Mon FragmentActivity contient un tas de données qui doivent être affichées de différentes manières sur différentes pages de ViewPager. Jusqu'à présent, je n'ai que 3 instances du même ListFragment, mais à l'avenir, j'aurai 3 instances de ListFragments différents. Le ViewPager est sur un écran de téléphone vertical, les listes ne sont pas côte à côte.
Désormais, un bouton du ListFragment lance une activité complète d'une page (via FragmentActivity), qui retourne et FragmentActivity modifie les données, les enregistre, puis tente de mettre à jour toutes les vues de son ViewPager. C'est ici, où je suis coincé.
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;
}
...
Maintenant, comme vous pouvez le constater, ma première tentative a été de notifyDataSetChanged sur l'ensemble de FragmentPagerAdapter, ce qui a parfois permis de mettre à jour les données, mais d'autres ont reçu une exception IllegalStateException: impossible d'effectuer cette action après onSaveInstanceState.
Ma deuxième tentative impliquait d'appeler une fonction de mise à jour dans ListFragment, mais getId dans getItem renvoyait 0. D'après les documents que j'ai essayés,
acquisition d'une référence au fragment de FragmentManager à l'aide de findFragmentById () ou findFragmentByTag ()
mais je ne connais ni l'étiquette ni l'identifiant de mes fragments! J'ai un Android: id = "@ + id/viewpager" pour ViewPager et un Android: id = "@ Android: id/list" pour mon ListView dans la présentation de ListFragment, mais je ne pense pas que cela soit utile.
Alors, comment puis-je: a) mettre à jour l’ensemble du ViewPager en toute sécurité en une fois (dans l’idéal, ramener l’utilisateur à la page sur laquelle il était auparavant) - il est normal que l’utilisateur voie la modification de la vue. Ou, de préférence, b) appelez une fonction dans chaque ListFragment concerné pour mettre à jour le ListView manuellement.
Toute aide serait acceptée avec reconnaissance!
Essayez d’enregistrer la balise chaque fois qu’un fragment est instancié.
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);
}
}
La réponse de Barkside fonctionne avec FragmentPagerAdapter
mais ne fonctionne pas avec FragmentStatePagerAdapter
, car elle ne définit pas les balises sur les fragments mais passe à FragmentManager
.
Avec FragmentStatePagerAdapter
, il semble que nous puissions nous en sortir en utilisant son appel instantiateItem(ViewGroup container, int position)
. Il retourne la référence au fragment à la position position
. Si FragmentStatePagerAdapter
contient déjà une référence au fragment en question, instantiateItem
ne fait que renvoyer une référence à ce fragment et n'appelle pas getItem()
pour l'instancier à nouveau.
Supposons donc que je suis en train de regarder le fragment n ° 50 et que je souhaite accéder au fragment n ° 49. Comme ils sont proches, il y a de fortes chances que le n ° 49 soit déjà instancié. Alors,
ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)
OK, je pense avoir trouvé un moyen d'exécuter la requête b) dans ma propre question, je vais donc partager pour le bénéfice des autres. La balise de fragments à l'intérieur d'un ViewPager est sous la forme "Android:switcher:VIEWPAGER_ID:INDEX"
, Où VIEWPAGER_ID
Est le R.id.viewpager
Dans la présentation XML et INDEX est la position dans le viewpager. Donc si la position est connue (par exemple 0), je peux jouer dans 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
}
}
Je ne sais pas si c'est une façon valable de le faire, mais ça ira jusqu'à ce que quelque chose de mieux soit suggéré.
Si vous me le demandez, la deuxième solution de la page ci-dessous, qui consiste à garder une trace de toutes les pages de fragments "actifs", est préférable: http://tamsler.blogspot.nl/2011/11/Android-viewpager-and -fragments-part-ii.html
La réponse de Barkside est trop compliquée pour moi.
vous gardez une trace de toutes les pages de fragments "actives". Dans ce cas, vous gardez une trace des pages de fragment dans FragmentStatePagerAdapter, utilisé par ViewPager.
private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();
public Fragment getItem(int index) {
Fragment myFragment = MyFragment.newInstance();
mPageReferences.put(index, myFragment);
return myFragment;
}
Pour éviter de conserver une référence aux pages de fragments "inactives", nous devons implémenter la méthode destroyItem (...) de FragmentStatePagerAdapter:
public void destroyItem(View container, int position, Object object) {
super.destroyItem(container, position, object);
mPageReferences.remove(position);
}
... et lorsque vous avez besoin d'accéder à la page actuellement visible, vous appelez ensuite:
int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);
... où la méthode getFragment (int) de MyAdapter ressemble à ceci:
public MyFragment getFragment(int key) {
return mPageReferences.get(key);
}
"
D'accord, après avoir testé la méthode par @barkside ci-dessus, je ne pouvais pas la faire fonctionner avec mon application. Ensuite, je me suis rappelé que IOSched2012 app utilise également un viewpager
, et c’est là que j’ai trouvé ma solution. Il n'utilise aucun identifiant de fragment ni étiquette, car ceux-ci ne sont pas stockés par viewpager
de manière facilement accessible.
Voici les parties importantes des applications IOSched HomeActivity. Portez une attention particulière au commentaire , car c'est là que réside la clé:
@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");
}
}
Et stocker des instances de vous Fragments
dans le FragmentPagerAdapter
comme ceci:
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;
}
Aussi, n'oubliez pas de garder vos appels Fragment
comme ceci:
if (mSocialStreamFragment != null) {
mSocialStreamFragment.refresh();
}
Vous pouvez copier FragmentPagerAdapter
et modifier du code source, ajouter la méthode getTag()
par exemple
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);
}
puis étendez-le, remplacez cette méthode abstraite, ne craignez rien Android Changement de groupe
FragmentPageAdapter
le code source à l'avenir
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();
}
}
Fonctionne également sans problème:
quelque part dans la mise en page de fragment de page:
<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>
dans fragment de 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;
et méthode pour retourner Fragment à partir de ViewPager, s'il existe:
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();
}
Vous pouvez également remplacer la méthode setPrimaryItem
de FragmentPagerAdapter
comme suit:
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;
}
Je veux donner mon approche au cas où cela pourrait aider quelqu'un d'autre:
Ceci est mon adaptateur de pager:
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;
}
}
Dans mon activité j'ai:
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);
...
}
}
Ensuite, pour obtenir le fragment actuel, ce que je fais est:
int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);
C'est ma solution car je n'ai pas besoin de suivre mes onglets et de les actualiser tous de toute façon.
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);
}
}