J'essaie d'utiliser Fragment avec un ViewPager
utilisant le FragmentPagerAdapter
. Ce que je cherche à réaliser, c'est de remplacer un fragment, positionné sur la première page du ViewPager
, par un autre.
Le téléavertisseur est composé de deux pages. Le premier est le FirstPagerFragment
, le second est le SecondPagerFragment
. Cliquer sur un bouton de la première page. Je voudrais remplacer le FirstPagerFragment
par le NextFragment.
Il y a mon code ci-dessous.
public class FragmentPagerActivity extends FragmentActivity {
static final int NUM_ITEMS = 2;
MyAdapter mAdapter;
ViewPager mPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_pager);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
}
/**
* Pager Adapter
*/
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
if(position == 0) {
return FirstPageFragment.newInstance();
} else {
return SecondPageFragment.newInstance();
}
}
}
/**
* Second Page FRAGMENT
*/
public static class SecondPageFragment extends Fragment {
public static SecondPageFragment newInstance() {
SecondPageFragment f = new SecondPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.second, container, false);
}
}
/**
* FIRST PAGE FRAGMENT
*/
public static class FirstPageFragment extends Fragment {
Button button;
public static FirstPageFragment newInstance() {
FirstPageFragment f = new FirstPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
View root = inflater.inflate(R.layout.first, container, false);
button = (Button) root.findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(null);
trans.commit();
}
});
return root;
}
/**
* Next Page FRAGMENT in the First Page
*/
public static class NextFragment extends Fragment {
public static NextFragment newInstance() {
NextFragment f = new NextFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.next, container, false);
}
}
}
... et ici les fichiers xml
fragment_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:padding="4dip"
android:gravity="center_horizontal"
android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
</LinearLayout>
first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/first_fragment_root_id"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/button"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="to next"/>
</LinearLayout>
Maintenant, le problème ... quel ID dois-je utiliser dans
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
?
Si j'utilise R.id.first_fragment_root_id
, le remplacement fonctionne, mais Hierarchy Viewer montre un comportement étrange, comme ci-dessous.
Au début, la situation est
après le remplacement, la situation est
Comme vous pouvez le voir, il y a quelque chose qui ne va pas, je m'attends à trouver le même état que celui indiqué dans la première image après avoir remplacé le fragment.
Réponses:
Il existe une autre solution qui n'a pas besoin de modifier le code source de
ViewPager
etFragmentStatePagerAdapter
, et elle fonctionne avec laFragmentPagerAdapter
classe de base utilisée par l'auteur.J'aimerais commencer par répondre à la question de l'auteur sur la pièce d'identité à utiliser; c'est l'ID du conteneur, c'est-à-dire l'ID du téléavertisseur lui-même. Cependant, comme vous l'avez probablement remarqué, l'utilisation de cet ID dans votre code ne provoque rien. Je vais vous expliquer pourquoi:
Tout d'abord, pour faire
ViewPager
repeupler les pages, vous devez appelernotifyDataSetChanged()
qui réside dans la classe de base de votre adaptateur.Deuxièmement,
ViewPager
utilise lagetItemPosition()
méthode abstraite pour vérifier quelles pages doivent être détruites et lesquelles doivent être conservées. L'implémentation par défaut de cette fonction revient toujoursPOSITION_UNCHANGED
, ce qui entraîne laViewPager
conservation de toutes les pages actuelles et, par conséquent, la non-association de votre nouvelle page. Ainsi, pour que le remplacement de fragment fonctionne, ilgetItemPosition()
doit être remplacé dans votre adaptateur et doit revenirPOSITION_NONE
lorsqu'il est appelé avec un ancien fragment à masquer comme argument.Cela signifie également que votre adaptateur doit toujours savoir quel fragment doit être affiché en position 0
FirstPageFragment
ouNextFragment
. Une façon de procéder consiste à fournir un écouteur lors de la créationFirstPageFragment
, qui sera appelé lorsqu'il sera temps de changer de fragment. Je pense que c'est une bonne chose cependant, de laisser votre adaptateur de fragment gérer tous les commutateurs de fragment et les appels àViewPager
etFragmentManager
.Troisièmement,
FragmentPagerAdapter
met en cache les fragments utilisés par un nom dérivé de la position, donc s'il y avait un fragment à la position 0, il ne sera pas remplacé même si la classe est nouvelle. Il existe deux solutions, mais la plus simple consiste à utiliser laremove()
fonction deFragmentTransaction
, qui supprimera également sa balise.C'était beaucoup de texte, voici du code qui devrait fonctionner dans votre cas:
J'espère que cela aide n'importe qui!
la source
FirstPageFragment.newInstance()
paramètre d'écoute?Depuis le 13 novembre 2012, le remplacement de fragments dans un ViewPager semble être devenu beaucoup plus facile. Google a publié Android 4.2 avec la prise en charge des fragments imbriqués, et il est également pris en charge dans la nouvelle bibliothèque de support Android v11, donc cela fonctionnera jusqu'à 1.6
C'est très similaire à la façon normale de remplacer un fragment, sauf que vous utilisez getChildFragmentManager. Il semble fonctionner sauf que le backstack de fragments imbriqués n'est pas sauté lorsque l'utilisateur clique sur le bouton de retour. Selon la solution de cette question liée, vous devez appeler manuellement le popBackStackImmediate () sur le gestionnaire enfant du fragment. Vous devez donc remplacer onBackPressed () de l'activité ViewPager où vous obtiendrez le fragment actuel de ViewPager et appelez getChildFragmentManager (). PopBackStackImmediate () dessus.
Obtenir le fragment actuellement affiché est également un peu hacky, j'ai utilisé cette sale solution "android: switcher: VIEWPAGER_ID: INDEX" mais vous pouvez également suivre vous-même tous les fragments du ViewPager comme expliqué dans la deuxième solution sur cette page .
Voici donc mon code pour un ViewPager avec 4 ListViews avec une vue détaillée affichée dans le ViewPager lorsque l'utilisateur clique sur une ligne, et avec le bouton de retour en marche. J'ai essayé d'inclure uniquement le code pertinent par souci de concision, alors laissez un commentaire si vous voulez que l'application complète soit téléchargée sur GitHub.
HomeActivity.java
ListProductsFragment.java
la source
Sur la base de la réponse de @wize, que j'ai trouvée utile et élégante, j'ai pu réaliser ce que je voulais en partie, car je voulais que la capacité revienne au premier fragment une fois remplacé. Je l'ai réalisé en modifiant un peu son code.
Ce serait le FragmentPagerAdapter:
Pour effectuer le remplacement, il suffit de définir un champ statique, du type
CalendarPageFragmentListener
et initialisé par lesnewInstance
méthodes des fragments correspondants et d'appelerFirstFragment.pageListener.onSwitchToNextFragment()
ouNextFragment.pageListener.onSwitchToNextFragment()
respictevely.la source
mFragmentAtPos0
référence lors de l'enregistrement de l'état d'activité. Ce n'est pas la solution la plus élégante, mais ça marche.J'ai implémenté une solution pour:
Les astuces pour y parvenir sont les suivantes:
Le code de l'adaptateur est le suivant:
La toute première fois que vous ajoutez tous les onglets, nous devons appeler la méthode createHistory (), pour créer l'historique initial
Chaque fois que vous souhaitez remplacer un fragment par un onglet spécifique que vous appelez: replace (position int finale, classe Class fragmentClass finale, arguments Bundle finaux)
À l'arrière, vous devez appeler la méthode back ():
La solution fonctionne avec la barre d'action sherlock et avec un geste de balayage.
la source
tl; dr: utilisez un fragment d'hôte qui est responsable du remplacement de son contenu hébergé et garde une trace de l'historique de navigation (comme dans un navigateur).
Comme votre cas d'utilisation se compose d'un nombre fixe d'onglets, ma solution fonctionne bien: L'idée est de remplir le ViewPager avec des instances d'une classe personnalisée
HostFragment
, capable de remplacer son contenu hébergé et de conserver son propre historique de navigation. Pour remplacer le fragment hébergé, vous appelez la méthodehostfragment.replaceFragment()
:Tout ce que cette méthode fait est de remplacer la disposition du cadre par l'id
R.id.hosted_fragment
avec le fragment fourni à la méthode.Consultez mon tutoriel sur ce sujet pour plus de détails et un exemple de travail complet sur GitHub!
la source
Certaines des solutions présentées m'ont beaucoup aidé à résoudre partiellement le problème, mais il y a encore une chose importante manquante dans les solutions qui a produit des exceptions inattendues et du contenu de page noire au lieu de fragmenter le contenu dans certains cas.
Le fait est que la classe FragmentPagerAdapter utilise l'ID d'élément pour stocker les fragments mis en cache dans FragmentManager . Pour cette raison, vous devez également remplacer la méthode getItemId (int position) afin qu'elle renvoie par exemple la position pour les pages de niveau supérieur et la position 100+ pour les pages de détails. Sinon, le fragment de niveau supérieur précédemment créé serait renvoyé du cache au lieu d'un fragment de niveau détail.
De plus, je partage ici un exemple complet comment implémenter une activité de type onglet avec des pages de fragment à l' aide de ViewPager et des boutons d'onglet à l'aide de RadioGroup qui permet le remplacement des pages de niveau supérieur par des pages détaillées et prend également en charge le bouton de retour. Cette implémentation prend en charge un seul niveau d'empilement arrière (liste d'éléments - détails des éléments) mais l'implémentation d'empilement arrière à plusieurs niveaux est simple. Cet exemple fonctionne assez bien dans les cas normaux, sauf qu'il lance une exception NullPointerException au cas où vous passez par exemple à la deuxième page, changez le fragment de la première page (bien qu'il ne soit pas visible) et revenez à la première page. Je publierai une solution à ce problème une fois que j'aurai compris:
la source
J'ai créé un ViewPager avec 3 éléments et 2 sous-éléments pour les index 2 et 3 et voici ce que je voulais faire ..
J'ai implémenté cela avec l'aide des questions et réponses précédentes de StackOverFlow et voici le lien.
ViewPagerChildFragments
la source
Pour remplacer un fragment dans un
ViewPager
vous pouvez déplacer les codes sources deViewPager
,PagerAdapter
et lesFragmentStatePagerAdapter
classes dans votre projet et ajoutez le code suivant.en
ViewPager
:dans FragmentStatePagerAdapter:
handleGetItemInvalidated()
garantit qu'après son prochain appel,getItem()
newFragmentgetFragmentPosition()
renvoie la position du fragment dans votre adaptateur.Maintenant, pour remplacer les fragments, appelez
Si vous êtes intéressé par un exemple de projet, demandez-moi les sources.
la source
Fonctionne très bien avec la solution d'AndroidTeam, mais j'ai trouvé que j'avais besoin de pouvoir revenir en arrière,
FrgmentTransaction.addToBackStack(null)
mais simplement ajouter cela ne fera que remplacer le fragment sans avertir le ViewPager. La combinaison de la solution fournie avec cette amélioration mineure vous permettra de revenir à l'état précédent en remplaçant simplement laonBackPressed()
méthode de l'activité . Le plus gros inconvénient est qu'il ne reviendra qu'un par un, ce qui peut entraîner plusieurs clics en arrièreJ'espère que cela aide quelqu'un.
Aussi loin que
getFragmentPosition()
cela se passe, c'est à peu prèsgetItem()
à l'envers. Vous savez quels fragments vont où, assurez-vous de renvoyer la position correcte dans laquelle ils se trouveront. Voici un exemple:la source
Dans votre
onCreateView
méthode,container
est en fait uneViewPager
instance.Donc, juste appeler
changera le fragment actuel dans votre
ViewPager
.la source
vpViewPager.setCurrentItem(1);
. J'ai parcouru l'exemple de chaque personne, sans que rien ne se passe à chaque fois jusqu'à ce que j'arrive enfin au vôtre. Je vous remercie.ViewPager
sur une autre page, il ne remplacera aucun fragment.Voici ma solution relativement simple à ce problème. Les clés de cette solution sont à utiliser
FragmentStatePagerAdapter
au lieu de,FragmentPagerAdapter
car la première supprimera les fragments inutilisés pour vous tandis que la dernière conserve toujours leurs instances. La seconde est l'utilisation dePOSITION_NONE
dans getItem (). J'ai utilisé une simple liste pour garder une trace de mes fragments. Mon exigence était de remplacer la liste complète des fragments à la fois par une nouvelle liste, mais celle-ci pourrait être facilement modifiée pour remplacer les fragments individuels:la source
J'ai également fait une solution, qui fonctionne avec Stacks . C'est une approche plus modulaire , donc vous n'avez pas besoin de spécifier chaque fragment et fragment de détail dans votre
FragmentPagerAdapter
. Il s'appuie sur l'exemple d'ActionbarSherlock qui dérive si j'ai raison de l'application de démonstration Google.Ajoutez ceci pour la fonctionnalité du bouton de retour dans votre MainActivity:
Si vous souhaitez enregistrer l'état du fragment lorsqu'il est supprimé. Laissez votre fragment implémenter l'interface
SaveStateBundle
renvoyer dans la fonction un ensemble avec votre état de sauvegarde. Obtenez le bundle après instanciation parthis.getArguments()
.Vous pouvez instancier un onglet comme celui-ci:
fonctionne de façon similaire si vous voulez ajouter un fragment au-dessus d'une pile d'onglets. Important : Je pense que cela ne fonctionnera pas si vous voulez avoir 2 instances de la même classe au-dessus de deux onglets. J'ai fait cette solution rapidement ensemble, donc je ne peux que la partager sans fournir aucune expérience avec elle.
la source
Le remplacement de fragments dans un viewpager est assez compliqué mais est très possible et peut sembler super lisse. Tout d'abord, vous devez laisser le viseur lui-même gérer la suppression et l'ajout des fragments. Ce qui se passe, c'est lorsque vous remplacez le fragment à l'intérieur de SearchFragment, votre viewpager conserve ses vues de fragment. Vous vous retrouvez donc avec une page vierge car le SearchFragment est supprimé lorsque vous essayez de le remplacer.
La solution consiste à créer un écouteur à l'intérieur de votre viewpager qui gérera les modifications effectuées à l'extérieur de celui-ci, ajoutez d'abord ce code au bas de votre adaptateur.
Ensuite, vous devez créer une classe privée dans votre viewpager qui devient un écouteur lorsque vous souhaitez modifier votre fragment. Par exemple, vous pouvez ajouter quelque chose comme ça. Notez qu'il implémente l'interface qui vient d'être créée. Ainsi, chaque fois que vous appelez cette méthode, elle exécutera le code à l'intérieur de la classe ci-dessous.
Il y a deux choses principales à souligner ici:
Remarquez les écouteurs placés dans le constructeur 'newInstance (écouteur). Voici comment vous appellerezfragment0Changed (String newFragmentIdentification) `. Le code suivant montre comment vous créez l'écouteur à l'intérieur de votre fragment.
static nextFragmentListener listenerSearch;
Vous pourriez appeler le changement à l'intérieur de votre
onPostExecute
Cela déclencherait le code à l'intérieur de votre viewpager pour commuter votre fragment à la position zéro fragAt0 pour devenir un nouveau searchResultFragment. Il y a deux autres petits morceaux que vous devez ajouter au viseur avant qu'il ne devienne fonctionnel.
L'une serait dans la méthode de remplacement getItem du viewpager.
Maintenant, sans cette dernière pièce, vous obtiendrez toujours une page blanche. Un peu boiteux, mais c'est une partie essentielle du viewPager. Vous devez remplacer la méthode getItemPosition du viewpager. Normalement, cette méthode retournera POSITION_UNCHANGED qui indique au viewpager de garder tout le même et ainsi getItem ne sera jamais appelé pour placer le nouveau fragment sur la page. Voici un exemple de quelque chose que vous pourriez faire
Comme je l'ai dit, le code est très impliqué, mais vous devez essentiellement créer un adaptateur personnalisé pour votre situation. Les choses que j'ai mentionnées permettront de changer le fragment. Il faudra probablement beaucoup de temps pour tout tremper, donc je serais patient, mais tout cela aura du sens. Cela vaut vraiment la peine de prendre le temps car cela peut rendre une application très élégante.
Voici le nugget pour gérer le bouton de retour. Vous mettez cela dans votre MainActivity
Vous devrez créer une méthode appelée backPressed () à l'intérieur de FragmentSearchResults qui appelle fragment0changed. Ceci en tandem avec le code que j'ai montré précédemment gérera la pression sur le bouton de retour. Bonne chance avec votre code pour changer le viseur. Cela prend beaucoup de travail, et pour autant que je l'ai trouvé, il n'y a pas d'adaptations rapides. Comme je l'ai dit, vous créez essentiellement un adaptateur de viseur personnalisé et le laissez gérer toutes les modifications nécessaires à l'aide d'écouteurs
la source
C'est ma façon d'y parvenir.
Tout d'abord, ajoutez
Root_fragment
l'viewPager
onglet intérieur dans lequel vous souhaitez implémenter l'fragment
événement de clic de bouton . Exemple;Tout d'abord,
RootTabFragment
devrait être inclusFragmentLayout
pour le changement de fragment.Ensuite, à l'intérieur
RootTabFragment
onCreateView
, implémentezfragmentChange
pour votreFirstPagerFragment
Après cela, implémentez l'
onClick
événement pour votre bouton à l'intérieurFirstPagerFragment
et modifiez à nouveau le fragment comme ça.J'espère que cela vous aidera.
la source
J'ai trouvé une solution simple, qui fonctionne bien même si vous souhaitez ajouter de nouveaux fragments au milieu ou remplacer le fragment actuel. Dans ma solution, vous devez remplacer
getItemId()
ce qui devrait retourner un identifiant unique pour chaque fragment. Ne pas positionner comme par défaut.Ça y est:
Remarque: Dans cet exemple
FirstFragment
etSecondFragment
étend la classe abstraite PageFragment, qui a une méthodegetPage()
.la source
Je fais quelque chose de similaire à wize mais dans ma réponse, vous pouvez changer entre les deux fragments quand vous le souhaitez. Et avec la réponse wize, j'ai des problèmes lorsque je change l'orientation de l'écran et des choses comme ça. Voici à quoi ressemble le PagerAdapter:
L'écouteur que j'ai implémenté dans l'activité de conteneur d'adaptateur pour le mettre dans le fragment lors de son attachement, voici l'activité:
Puis dans le fragment mettant l'auditeur lors de l'attachement d'un appelant:
Et enfin l'auditeur:
la source
J'ai suivi les réponses de @wize et @mdelolmo et j'ai obtenu la solution. Merci beaucoup. Mais, j'ai un peu réglé ces solutions pour améliorer la consommation de mémoire.
Problèmes que j'ai observés:
Ils enregistrent l'instance
Fragment
qui est remplacée. Dans mon cas, c'est un fragment qui tientMapView
et j'ai pensé que c'était cher. Donc, je maintiens leFragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)
lieu deFragment
lui - même.Voici ma mise en œuvre.
Lien de démonstration ici .. https://youtu.be/l_62uhKkLyM
À des fins de démonstration, utilisé 2 fragments
TabReplaceFragment
etDemoTab2Fragment
en position deux. Dans tous les autres cas, j'utilise desDemoTabFragment
instances.Explication:
Je passe
Switch
d'Activité àDemoCollectionPagerAdapter
. En fonction de l'état de ce commutateur, nous afficherons le fragment correct. Lorsque la vérification du commutateur est modifiée, j'appelle la méthodeSwitchFragListener
'sonSwitchToNextFragment
, où je change la valeur depagerAdapterPosChanged
variable enPOSITION_NONE
. En savoir plus sur POSITION_NONE . Cela invalidera le getItem et j'ai des logiques pour instancier le bon fragment là-bas. Désolé, si l'explication est un peu compliquée.Encore une fois merci à @wize et @mdelolmo pour l'idée originale.
J'espère que cela vous sera utile. :)
Faites-moi savoir si cette mise en œuvre présente des défauts. Ce sera très utile pour mon projet.
la source
après des recherches, j'ai trouvé une solution avec un code court. tout d'abord, créez une instance publique sur fragment et supprimez simplement votre fragment sur onSaveInstanceState si le fragment ne se recrée pas lors du changement d'orientation.
la source