Question

J'utilise la compatibilité v4 ViewPager sous Android. Mon FragmentActivity a un tas de données qui doivent être affichées de différentes manières sur différentes pages de mon 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.

À présent, un bouton sur le ListFragment démarre une activité pleine page distincte (via FragmentActivity), qui retourne et FragmentActivity modifie les données, les enregistre, puis tente de mettre à jour toutes les vues dans 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 voir, ma première tentative a été de notifierDataSetChanged sur tout le FragmentPagerAdapter, et cela a montré qu'il fallait parfois mettre à jour les données, mais d'autres j'ai eu une IllegalStateException: Impossible d'exécuter cette action après onSaveInstanceState.

Ma deuxième tentative consistait à essayer d'appeler une fonction de mise à jour dans mon ListFragment, mais getId dans getItem a renvoyé 0. Selon les documents que j'ai essayés par

acquisition d'une référence au fragment à partir de FragmentManager, en utilisant findFragmentById () ou findFragmentByTag ()

mais je ne connais pas la balise ou 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 mise en page ListFragment, mais je ne pense pas que ceux-ci soient utiles.

Alors, comment puis-je: a) mettre à jour l'intégralité du ViewPager en toute sécurité en une seule fois (idéalement en renvoyant l'utilisateur à la page sur laquelle il se trouvait auparavant) - il est normal que l'utilisateur voit la vue changer. Ou de préférence, b) appeler une fonction dans chaque ListFragment concerné pour mettre à jour la ListView manuellement.

Toute aide sera acceptée avec gratitude!

Était-ce utile?

La solution

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

Autres conseils

La réponse de Barkside fonctionne avec FragmentPagerAdapter mais ne fonctionne pas avec FragmentStatePagerAdapter, car il ne définit pas de balises sur les fragments qu'il transmet à FragmentManager.

Avec FragmentStatePagerAdapter, il semble que nous puissions nous en sortir, en utilisant son appel instantiateItem(ViewGroup container, int position).Il renvoie la référence au fragment à la position position.Si FragmentStatePagerAdapter contient déjà une référence au fragment en question, instantiateItem renvoie simplement la référence à ce fragment et n'appelle pas getItem() pour l'instancier à nouveau.

Supposons donc que je regarde actuellement le fragment # 50 et que je souhaite accéder au fragment # 49.Comme ils sont proches, il y a de fortes chances que le # 49 soit déjà instancié.Donc,

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 afin que je la partage pour le bénéfice des autres.La balise des fragments à l'intérieur d'un ViewPager se présente sous la forme "android:switcher:VIEWPAGER_ID:INDEX", où VIEWPAGER_ID est le R.id.viewpager dans la mise en page XML et INDEX est la position dans le viewpager.Donc si la position est connue (par exemple 0), je peux effectuer en 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 manière valable de le faire, mais cela fonctionnera jusqu'à ce que quelque chose de mieux soit suggéré.

Si vous me le demandez, la deuxième solution de la page ci-dessous, garder une trace de toutes les pages de fragments "actives", est préférable: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part-ii.html

La réponse de barkside est trop hacker 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 le FragmentStatePagerAdapter, qui est utilisé par le 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 garder une référence à des 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 alors:

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 n'ai pas pu la faire fonctionner avec mon application.Ensuite, je me suis souvenu que l ' application IOSched2012 utilise également un viewpager, et c'est là que j'ai trouvé ma solution.Il n'utilise aucun ID de fragment ou balise car ceux-ci ne sont pas stockés par viewpager d'une manière facilement accessible.

Voici le parties importantes des applications IOSched HomeActivity.Faites particulièrement attention 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 stockez les instances de votre 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;
    }

N'oubliez pas non plus 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 ces méthodes abstraites, ne devez pas avoir peur du changement de groupe Android

code source FragmentPageAdapter dans le futur

 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 du 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 onCreateView () du fragment:

...
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 renvoyer le fragment depuis 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;
}

I want to give my approach in case it can help anyone else:

This is my pager adapter:

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

}

In my activity I have:

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

}

Then to get the current fragment what I do is:

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

This is my solution since I don't need to keep track of my tabs and need to refresh them all anyway.

    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);
        }
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top