Remplacer un Fragment dans un ViewPager
J'essaie D'utiliser un Fragment avec un ViewPager
en utilisant le FragmentPagerAdapter
.
Ce que je cherche à réaliser, c'est de remplacer un fragment , placé sur la première page du ViewPager
, par un autre.
le pager est composé de deux pages. Le premier est le FirstPagerFragment
, le second est le SecondPagerFragment
. Cliquer sur un bouton de la première page. J'aimerais remplacer le FirstPagerFragment
par le prochain fragment.
il y a mon le 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 voici 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... quelle pièce D'identité dois-je utiliser dans
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
?
si j'utilise R.id.first_fragment_root_id
, les œuvres de remplacement, mais le spectateur hiérarchie 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 sur la première image après avoir remplacé le fragment.
18 réponses
il y a une autre solution qui n'a pas besoin de modifier le code source de ViewPager
et FragmentStatePagerAdapter
, et qui fonctionne avec la classe de base FragmentPagerAdapter
utilisée par l'auteur.
j'aimerais commencer par répondre à la question de l'auteur sur l'ID QU'il devrait utiliser; C'est L'ID du conteneur, c'est-à-dire L'ID du pager de vue lui-même. Cependant, comme vous l'avez probablement remarqué vous-même, 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 appeler notifyDataSetChanged()
qui réside dans la classe de base de votre adaptateur.
Second, ViewPager
utilise la méthode abstraite getItemPosition()
pour vérifier quelles pages doivent être détruites et lesquelles doivent être conservées. L'implémentation par défaut de cette fonction renvoie toujours POSITION_UNCHANGED
, ce qui fait que ViewPager
conserve toutes les pages actuelles, et par conséquent ne pas joindre votre nouvelle page. Ainsi, pour faire travail de remplacement de fragment, getItemPosition()
doit être dépassé dans votre adaptateur et doit retourner POSITION_NONE
lorsqu'il est appelé avec un vieux, pour être caché, fragment comme argument.
cela signifie également que votre adaptateur doit toujours être conscient du fragment qui doit être affiché en position 0, FirstPageFragment
ou NextFragment
. Une façon de le faire est de fournir un auditeur lors de la création de FirstPageFragment
, qui sera appelé quand il est temps de changer de fragments. Je pense que c' est une bonne chose cependant, pour laisser votre adaptateur de fragment gérer tous les commutateurs de fragment et les appels à ViewPager
et FragmentManager
.
Troisièmement, FragmentPagerAdapter
cache les fragments utilisés par un nom qui est 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 y a deux solutions, mais le plus simple est d'utiliser la fonction remove()
de FragmentTransaction
, qui supprimera son étiquette ainsi.
C'était beaucoup de texte, voici le code qui devrait fonctionner dans votre cas:
public class MyAdapter extends FragmentPagerAdapter
{
static final int NUM_ITEMS = 2;
private final FragmentManager mFragmentManager;
private Fragment mFragmentAtPos0;
public MyAdapter(FragmentManager fm)
{
super(fm);
mFragmentManager = fm;
}
@Override
public Fragment getItem(int position)
{
if (position == 0)
{
if (mFragmentAtPos0 == null)
{
mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener()
{
public void onSwitchToNextFragment()
{
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
mFragmentAtPos0 = NextFragment.newInstance();
notifyDataSetChanged();
}
});
}
return mFragmentAtPos0;
}
else
return SecondPageFragment.newInstance();
}
@Override
public int getCount()
{
return NUM_ITEMS;
}
@Override
public int getItemPosition(Object object)
{
if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
}
public interface FirstPageFragmentListener
{
void onSwitchToNextFragment();
}
Espérons que cela aide quelqu'un!
depuis le 13 novembre 2012, il semble que le repositionnement des fragments dans un ViewPager soit 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 Android Support Library v11 donc cela fonctionnera tout le chemin du retour à 1.6
c'est très similaire à la façon normale de remplacer un fragment sauf que vous utilisez getChildFragmentManager. Il semble fonctionner sauf le fragment emboîté backsack n'est pas déclenché lorsque l'utilisateur clique sur le bouton arrière. Selon la solution de cette question liée, vous devez appeler manuellement popBackStackImmediate() sur le gestionnaire enfant du fragment. Vous devez donc surcharger onBackPressed () de L'activité ViewPager où vous obtiendrez le fragment courant du ViewPager et appellerez getChildFragmentManager ().popBackStackImmediate() sur celui-ci.
obtenir le Fragment actuellement affiché est un peu hacky aussi, j'ai utilisé ceci dirty" android: switcher: VIEWPAGER_ID: INDEX "solution mais vous pouvez également garder la trace de tous les fragments du ViewPager vous-même comme expliqué dans la deuxième solution sur cette page .
alors voici 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 arrière qui fonctionne. J'ai essayé d'inclure juste le code pertinent par souci de brièveté alors laisser un commentaire si vous voulez que L'application soit téléchargée sur GitHub.
HomeActivity.java
public class HomeActivity extends SherlockFragmentActivity {
FragmentAdapter mAdapter;
ViewPager mPager;
TabPageIndicator mIndicator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mAdapter = new FragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (TabPageIndicator)findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
}
// This the important bit to make sure the back button works when you're nesting fragments. Very hacky, all it takes is some Google engineer to change that ViewPager view tag to break this in a future Android update.
@Override
public void onBackPressed() {
Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":"+mPager.getCurrentItem());
if (fragment != null) // could be null if not instantiated yet
{
if (fragment.getView() != null) {
// Pop the backstack on the ChildManager if there is any. If not, close this activity as normal.
if (!fragment.getChildFragmentManager().popBackStackImmediate()) {
finish();
}
}
}
}
class FragmentAdapter extends FragmentPagerAdapter {
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ListProductsFragment.newInstance();
case 1:
return ListActiveSubstancesFragment.newInstance();
case 2:
return ListProductFunctionsFragment.newInstance();
case 3:
return ListCropsFragment.newInstance();
default:
return null;
}
}
@Override
public int getCount() {
return 4;
}
}
}
ListProductsFragment.java
public class ListProductsFragment extends SherlockFragment {
private ListView list;
public static ListProductsFragment newInstance() {
ListProductsFragment f = new ListProductsFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View V = inflater.inflate(R.layout.list, container, false);
list = (ListView)V.findViewById(android.R.id.list);
list.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// This is important bit
Fragment productDetailFragment = FragmentProductDetail.newInstance();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.addToBackStack(null);
transaction.replace(R.id.products_list_linear, productDetailFragment).commit();
}
});
return V;
}
}
basé sur la réponse de @wize, que j'ai trouvé utile et élégante, j'ai pu réaliser ce que je voulais en partie, parce que je voulais la compatibilité de revenir au premier Fragment une fois remplacé. J'ai réussi à modifier un peu son code.
ce serait le FragmentPagerAdapter:
public static class MyAdapter extends FragmentPagerAdapter {
private final class CalendarPageListener implements
CalendarPageFragmentListener {
public void onSwitchToNextFragment() {
mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
.commit();
if (mFragmentAtPos0 instanceof FirstFragment){
mFragmentAtPos0 = NextFragment.newInstance(listener);
}else{ // Instance of NextFragment
mFragmentAtPos0 = FirstFragment.newInstance(listener);
}
notifyDataSetChanged();
}
}
CalendarPageListener listener = new CalendarPageListener();;
private Fragment mFragmentAtPos0;
private FragmentManager mFragmentManager;
public MyAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public int getItemPosition(Object object) {
if (object instanceof FirstFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
@Override
public Fragment getItem(int position) {
if (position == 0)
return Portada.newInstance();
if (position == 1) { // Position where you want to replace fragments
if (mFragmentAtPos0 == null) {
mFragmentAtPos0 = FirstFragment.newInstance(listener);
}
return mFragmentAtPos0;
}
if (position == 2)
return Clasificacion.newInstance();
if (position == 3)
return Informacion.newInstance();
return null;
}
}
public interface CalendarPageFragmentListener {
void onSwitchToNextFragment();
}
pour effectuer le remplacement, il suffit de définir un champ statique, du type CalendarPageFragmentListener
et initialisé par les méthodes newInstance
des fragmente et appelle FirstFragment.pageListener.onSwitchToNextFragment()
ou NextFragment.pageListener.onSwitchToNextFragment()
respectivement.
Espère que c'est clair et utile.
En Ce Qui Concerne Meilleur.
j'ai mis en place une solution pour:
- Dynamique fragment de remplacement à l'intérieur de l'onglet
- entretien de l'historique par onglet
- travailler avec des changements d'orientation
Les astuces pour y parvenir sont les suivantes:
- utiliser la méthode notifyDataSetChanged() pour appliquer le remplacement du fragment
- utilisez le fragment gestionnaire seulement pour l'arrière-scène et non pour le remplacement de fragment
- Maintenir l'histoire en utilisant le pattern memento (pile)
le code adaptateur est le suivant:
public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
/** The sherlock fragment activity. */
private final SherlockFragmentActivity mActivity;
/** The action bar. */
private final ActionBar mActionBar;
/** The pager. */
private final ViewPager mPager;
/** The tabs. */
private List<TabInfo> mTabs = new LinkedList<TabInfo>();
/** The total number of tabs. */
private int TOTAL_TABS;
private Map<Integer, Stack<TabInfo>> history = new HashMap<Integer, Stack<TabInfo>>();
/**
* Creates a new instance.
*
* @param activity the activity
* @param pager the pager
*/
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
super(activity.getSupportFragmentManager());
activity.getSupportFragmentManager();
this.mActivity = activity;
this.mActionBar = activity.getSupportActionBar();
this.mPager = pager;
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}
/**
* Adds the tab.
*
* @param image the image
* @param fragmentClass the class
* @param args the arguments
*/
public void addTab(final Drawable image, final Class fragmentClass, final Bundle args) {
final TabInfo tabInfo = new TabInfo(fragmentClass, args);
final ActionBar.Tab tab = mActionBar.newTab();
tab.setTabListener(this);
tab.setTag(tabInfo);
tab.setIcon(image);
mTabs.add(tabInfo);
mActionBar.addTab(tab);
notifyDataSetChanged();
}
@Override
public Fragment getItem(final int position) {
final TabInfo tabInfo = mTabs.get(position);
return Fragment.instantiate(mActivity, tabInfo.fragmentClass.getName(), tabInfo.args);
}
@Override
public int getItemPosition(final Object object) {
/* Get the current position. */
int position = mActionBar.getSelectedTab().getPosition();
/* The default value. */
int pos = POSITION_NONE;
if (history.get(position).isEmpty()) {
return POSITION_NONE;
}
/* Checks if the object exists in current history. */
for (Stack<TabInfo> stack : history.values()) {
TabInfo c = stack.peek();
if (c.fragmentClass.getName().equals(object.getClass().getName())) {
pos = POSITION_UNCHANGED;
break;
}
}
return pos;
}
@Override
public int getCount() {
return mTabs.size();
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int position) {
mActionBar.setSelectedNavigationItem(position);
}
@Override
public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction ft) {
TabInfo tabInfo = (TabInfo) tab.getTag();
for (int i = 0; i < mTabs.size(); i++) {
if (mTabs.get(i).equals(tabInfo)) {
mPager.setCurrentItem(i);
}
}
}
@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
public void replace(final int position, final Class fragmentClass, final Bundle args) {
/* Save the fragment to the history. */
mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();
/* Update the tabs. */
updateTabs(new TabInfo(fragmentClass, args), position);
/* Updates the history. */
history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));
notifyDataSetChanged();
}
/**
* Updates the tabs.
*
* @param tabInfo
* the new tab info
* @param position
* the position
*/
private void updateTabs(final TabInfo tabInfo, final int position) {
mTabs.remove(position);
mTabs.add(position, tabInfo);
mActionBar.getTabAt(position).setTag(tabInfo);
}
/**
* Creates the history using the current state.
*/
public void createHistory() {
int position = 0;
TOTAL_TABS = mTabs.size();
for (TabInfo mTab : mTabs) {
if (history.get(position) == null) {
history.put(position, new Stack<TabInfo>());
}
history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
position++;
}
}
/**
* Called on back
*/
public void back() {
int position = mActionBar.getSelectedTab().getPosition();
if (!historyIsEmpty(position)) {
/* In case there is not any other item in the history, then finalize the activity. */
if (isLastItemInHistory(position)) {
mActivity.finish();
}
final TabInfo currentTabInfo = getPrevious(position);
mTabs.clear();
for (int i = 0; i < TOTAL_TABS; i++) {
if (i == position) {
mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
} else {
TabInfo otherTabInfo = history.get(i).peek();
mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
}
}
}
mActionBar.selectTab(mActionBar.getTabAt(position));
notifyDataSetChanged();
}
/**
* Returns if the history is empty.
*
* @param position
* the position
* @return the flag if empty
*/
private boolean historyIsEmpty(final int position) {
return history == null || history.isEmpty() || history.get(position).isEmpty();
}
private boolean isLastItemInHistory(final int position) {
return history.get(position).size() == 1;
}
/**
* Returns the previous state by the position provided.
*
* @param position
* the position
* @return the tab info
*/
private TabInfo getPrevious(final int position) {
TabInfo currentTabInfo = history.get(position).pop();
if (!history.get(position).isEmpty()) {
currentTabInfo = history.get(position).peek();
}
return currentTabInfo;
}
/** The tab info class */
private static class TabInfo {
/** The fragment class. */
public Class fragmentClass;
/** The args.*/
public Bundle args;
/**
* Creates a new instance.
*
* @param fragmentClass
* the fragment class
* @param args
* the args
*/
public TabInfo(Class fragmentClass, Bundle args) {
this.fragmentClass = fragmentClass;
this.args = args;
}
@Override
public boolean equals(final Object o) {
return this.fragmentClass.getName().equals(o.getClass().getName());
}
@Override
public int hashCode() {
return fragmentClass.getName() != null ? fragmentClass.getName().hashCode() : 0;
}
@Override
public String toString() {
return "TabInfo{" +
"fragmentClass=" + fragmentClass +
'}';
}
}
la toute première fois que vous ajoutez tous les onglets, nous devons appeler la méthode createHistory (), pour créer l'histoire initiale
public void createHistory() {
int position = 0;
TOTAL_TABS = mTabs.size();
for (TabInfo mTab : mTabs) {
if (history.get(position) == null) {
history.put(position, new Stack<TabInfo>());
}
history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
position++;
}
}
Chaque fois que vous voulez remplacer un fragment à un onglet spécifique que vous appelez: remplacer(final position int, classe finale fragmentClass, Final Bundle args)
/* Save the fragment to the history. */
mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();
/* Update the tabs. */
updateTabs(new TabInfo(fragmentClass, args), position);
/* Updates the history. */
history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));
notifyDataSetChanged();
sur le dos pressé vous devez rappeler la méthode back ():
public void back() {
int position = mActionBar.getSelectedTab().getPosition();
if (!historyIsEmpty(position)) {
/* In case there is not any other item in the history, then finalize the activity. */
if (isLastItemInHistory(position)) {
mActivity.finish();
}
final TabInfo currentTabInfo = getPrevious(position);
mTabs.clear();
for (int i = 0; i < TOTAL_TABS; i++) {
if (i == position) {
mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
} else {
TabInfo otherTabInfo = history.get(i).peek();
mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
}
}
}
mActionBar.selectTab(mActionBar.getTabAt(position));
notifyDataSetChanged();
}
la solution fonctionne avec la barre d'action de sherlock et avec le geste de glisser.
tl;dr: utilisez un fragment d'hôte qui est responsable de remplacer son contenu hébergé et garde la trace d'un historique de navigation (comme dans un navigateur).
comme votre cas d'utilisation se compose d'une quantité fixe d'onglets ma solution fonctionne bien: l'idée est de remplir le ViewPager avec des instances d'une classe personnalisée HostFragment
, qui est capable de remplacer son contenu hébergé et conserve sa propre histoire de navigation arrière. Pour remplacer le fragment hébergé vous faites un appel à la méthode hostfragment.replaceFragment()
:
public void replaceFragment(Fragment fragment, boolean addToBackstack) {
if (addToBackstack) {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
} else {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
}
}
cette méthode ne fait que remplacer la disposition du cadre par l'id R.id.hosted_fragment
par le fragment fourni à la méthode.
consultez mon tutoriel sur ce thème pour plus de détails et un exemple de travail complet sur GitHub!
certaines des solutions présentées m'ont beaucoup aidé à résoudre partiellement le problème, mais il manque encore une chose importante dans les solutions qui ont produit des exceptions inattendues et du contenu de page noire au lieu du contenu de fragment dans certains cas.
la chose est que FragmentPagerAdapter classe utilise L'élément ID pour stocker des fragments de cache à FragmentManager . Pour cette raison, vous avez besoin de remplacer également le getItemId(position int) méthode de sorte qu'elle retourne E. G. position pour les pages de premier niveau et 100 + position pour les pages de détails. Sinon, le fragment de niveau supérieur précédemment créé serait retourné à partir du cache au lieu du fragment de niveau de détail.
en outre, je partage ici un exemple complet Comment mettre en œuvre l'activité de type tabs avec Fragment pages utilisant ViewPager et les boutons de tabulation en utilisant RadioGroup qui permet le remplacement des pages de haut niveau avec des pages détaillées et soutient également le bouton de retour. Cette mise en œuvre ne prend en charge qu'un seul niveau d'empilage (liste des éléments - détails des éléments), mais la mise en œuvre de l'empilage à plusieurs niveaux est simple. Cet exemple fonctionne assez bien dans les cas normaux sauf qu'il lance un NullPointerException dans le cas où vous passez à E. G. deuxième page, changez le fragment de la première page (bien qu'il ne soit pas visible) et retournez à la première page. Je posterai une solution à ce problème une fois que j'aurai compris:
public class TabsActivity extends FragmentActivity {
public static final int PAGE_COUNT = 3;
public static final int FIRST_PAGE = 0;
public static final int SECOND_PAGE = 1;
public static final int THIRD_PAGE = 2;
/**
* Opens a new inferior page at specified tab position and adds the current page into back
* stack.
*/
public void startPage(int position, Fragment content) {
// Replace page adapter fragment at position.
mPagerAdapter.start(position, content);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Initialize basic layout.
this.setContentView(R.layout.tabs_activity);
// Add tab fragments to view pager.
{
// Create fragments adapter.
mPagerAdapter = new PagerAdapter(pager);
ViewPager pager = (ViewPager) super.findViewById(R.id.tabs_view_pager);
pager.setAdapter(mPagerAdapter);
// Update active tab in tab bar when page changes.
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int index, float value, int nextIndex) {
// Not used.
}
@Override
public void onPageSelected(int index) {
RadioGroup tabs_radio_group = (RadioGroup) TabsActivity.this.findViewById(
R.id.tabs_radio_group);
switch (index) {
case 0: {
tabs_radio_group.check(R.id.first_radio_button);
}
break;
case 1: {
tabs_radio_group.check(R.id.second_radio_button);
}
break;
case 2: {
tabs_radio_group.check(R.id.third_radio_button);
}
break;
}
}
@Override
public void onPageScrollStateChanged(int index) {
// Not used.
}
});
}
// Set "tabs" radio group on checked change listener that changes the displayed page.
RadioGroup radio_group = (RadioGroup) this.findViewById(R.id.tabs_radio_group);
radio_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup radioGroup, int id) {
// Get view pager representing tabs.
ViewPager view_pager = (ViewPager) TabsActivity.this.findViewById(R.id.tabs_view_pager);
if (view_pager == null) {
return;
}
// Change the active page.
switch (id) {
case R.id.first_radio_button: {
view_pager.setCurrentItem(FIRST_PAGE);
}
break;
case R.id.second_radio_button: {
view_pager.setCurrentItem(SECOND_PAGE);
}
break;
case R.id.third_radio_button: {
view_pager.setCurrentItem(THIRD_PAGE);
}
break;
}
});
}
}
@Override
public void onBackPressed() {
if (!mPagerAdapter.back()) {
super.onBackPressed();
}
}
/**
* Serves the fragments when paging.
*/
private class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(ViewPager container) {
super(TabsActivity.this.getSupportFragmentManager());
mContainer = container;
mFragmentManager = TabsActivity.this.getSupportFragmentManager();
// Prepare "empty" list of fragments.
mFragments = new ArrayList<Fragment>(){};
mBackFragments = new ArrayList<Fragment>(){};
for (int i = 0; i < PAGE_COUNT; i++) {
mFragments.add(null);
mBackFragments.add(null);
}
}
/**
* Replaces the view pager fragment at specified position.
*/
public void replace(int position, Fragment fragment) {
// Get currently active fragment.
Fragment old_fragment = mFragments.get(position);
if (old_fragment == null) {
return;
}
// Replace the fragment using transaction and in underlaying array list.
// NOTE .addToBackStack(null) doesn't work
this.startUpdate(mContainer);
mFragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.remove(old_fragment).add(mContainer.getId(), fragment)
.commit();
mFragments.set(position, fragment);
this.notifyDataSetChanged();
this.finishUpdate(mContainer);
}
/**
* Replaces the fragment at specified position and stores the current fragment to back stack
* so it can be restored by #back().
*/
public void start(int position, Fragment fragment) {
// Remember current fragment.
mBackFragments.set(position, mFragments.get(position));
// Replace the displayed fragment.
this.replace(position, fragment);
}
/**
* Replaces the current fragment by fragment stored in back stack. Does nothing and returns
* false if no fragment is back-stacked.
*/
public boolean back() {
int position = mContainer.getCurrentItem();
Fragment fragment = mBackFragments.get(position);
if (fragment == null) {
// Nothing to go back.
return false;
}
// Restore the remembered fragment and remove it from back fragments.
this.replace(position, fragment);
mBackFragments.set(position, null);
return true;
}
/**
* Returns fragment of a page at specified position.
*/
@Override
public Fragment getItem(int position) {
// If fragment not yet initialized, create its instance.
if (mFragments.get(position) == null) {
switch (position) {
case FIRST_PAGE: {
mFragments.set(FIRST_PAGE, new DefaultFirstFragment());
}
break;
case SECOND_PAGE: {
mFragments.set(SECOND_PAGE, new DefaultSecondFragment());
}
break;
case THIRD_PAGE: {
mFragments.set(THIRD_PAGE, new DefaultThirdFragment());
}
break;
}
}
// Return fragment instance at requested position.
return mFragments.get(position);
}
/**
* Custom item ID resolution. Needed for proper page fragment caching.
* @see FragmentPagerAdapter#getItemId(int).
*/
@Override
public long getItemId(int position) {
// Fragments from second level page hierarchy have their ID raised above 100. This is
// important to FragmentPagerAdapter because it is caching fragments to FragmentManager with
// this item ID key.
Fragment item = mFragments.get(position);
if (item != null) {
if ((item instanceof NewFirstFragment) || (item instanceof NewSecondFragment) ||
(item instanceof NewThirdFragment)) {
return 100 + position;
}
}
return position;
}
/**
* Returns number of pages.
*/
@Override
public int getCount() {
return mFragments.size();
}
@Override
public int getItemPosition(Object object)
{
int position = POSITION_UNCHANGED;
if ((object instanceof DefaultFirstFragment) || (object instanceof NewFirstFragment)) {
if (object.getClass() != mFragments.get(FIRST_PAGE).getClass()) {
position = POSITION_NONE;
}
}
if ((object instanceof DefaultSecondragment) || (object instanceof NewSecondFragment)) {
if (object.getClass() != mFragments.get(SECOND_PAGE).getClass()) {
position = POSITION_NONE;
}
}
if ((object instanceof DefaultThirdFragment) || (object instanceof NewThirdFragment)) {
if (object.getClass() != mFragments.get(THIRD_PAGE).getClass()) {
position = POSITION_NONE;
}
}
return position;
}
private ViewPager mContainer;
private FragmentManager mFragmentManager;
/**
* List of page fragments.
*/
private List<Fragment> mFragments;
/**
* List of page fragments to return to in onBack();
*/
private List<Fragment> mBackFragments;
}
/**
* Tab fragments adapter.
*/
private PagerAdapter mPagerAdapter;
}
j'ai créé un ViewPager avec 3 éléments et 2 sous-éléments pour l'indice 2 et 3 et voici ce que je voulais faire..
j'ai mis en œuvre ceci avec l'aide de questions et réponses précédentes de StackOverFlow et voici le lien.
pour remplacer un fragment à l'intérieur d'une ViewPager
vous pouvez déplacer les codes sources de ViewPager
, PagerAdapter
et FragmentStatePagerAdapter
dans votre projet et ajouter le code suivant.
en ViewPager
:
public void notifyItemChanged(Object oldItem, Object newItem) {
if (mItems != null) {
for (ItemInfo itemInfo : mItems) {
if (itemInfo.object.equals(oldItem)) {
itemInfo.object = newItem;
}
}
}
invalidate();
}
en FragmentStatePagerAdapter:
public void replaceFragmetns(ViewPager container, Fragment oldFragment, Fragment newFragment) {
startUpdate(container);
// remove old fragment
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
int position = getFragmentPosition(oldFragment);
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, null);
mFragments.set(position, null);
mCurTransaction.remove(oldFragment);
// add new fragment
while (mFragments.size() <= position) {
mFragments.add(null);
}
mFragments.set(position, newFragment);
mCurTransaction.add(container.getId(), newFragment);
finishUpdate(container);
// ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
handleGetItemInbalidated(container, oldFragment, newFragment);
container.notifyItemChanged(oldFragment, newFragment);
}
protected abstract void handleGetItemInbalidated(View container, Fragment oldFragment, Fragment newFragment);
protected abstract int getFragmentPosition(Fragment fragment);
handleGetItemInvalidated()
assure qu'après le prochain appel de getItem()
il renvoie newFragment
getFragmentPosition()
renvoie la position du fragment dans votre adaptateur.
maintenant, pour remplacer l'appel de fragments
mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);
si vous êtes intéressé par un exemple de projet, demandez-moi les sources.
fonctionne très bien avec la solution D'AndroidTeam, cependant j'ai trouvé que j'avais besoin de la capacité de revenir en arrière un peu comme FrgmentTransaction.addToBackStack(null)
mais en ajoutant simplement cela ne fera que remplacer le Fragment sans prévenir 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 la méthode onBackPressed()
de l'activité. Le plus grand inconvénient est qu'il ne fera que revenir à un moment qui peut entraîner plusieurs clics
private ArrayList<Fragment> bFragments = new ArrayList<Fragment>();
private ArrayList<Integer> bPosition = new ArrayList<Integer>();
public void replaceFragmentsWithBackOut(ViewPager container, Fragment oldFragment, Fragment newFragment) {
startUpdate(container);
// remove old fragment
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
int position = getFragmentPosition(oldFragment);
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
//Add Fragment to Back List
bFragments.add(oldFragment);
//Add Pager Position to Back List
bPosition.add(position);
mSavedState.set(position, null);
mFragments.set(position, null);
mCurTransaction.remove(oldFragment);
// add new fragment
while (mFragments.size() <= position) {
mFragments.add(null);
}
mFragments.set(position, newFragment);
mCurTransaction.add(container.getId(), newFragment);
finishUpdate(container);
// ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
handleGetItemInvalidated(container, oldFragment, newFragment);
container.notifyItemChanged(oldFragment, newFragment);
}
public boolean popBackImmediate(ViewPager container){
int bFragSize = bFragments.size();
int bPosSize = bPosition.size();
if(bFragSize>0 && bPosSize>0){
if(bFragSize==bPosSize){
int last = bFragSize-1;
int position = bPosition.get(last);
//Returns Fragment Currently at this position
Fragment replacedFragment = mFragments.get(position);
Fragment originalFragment = bFragments.get(last);
this.replaceFragments(container, replacedFragment, originalFragment);
bPosition.remove(last);
bFragments.remove(last);
return true;
}
}
return false;
}
J'espère que ça aidera quelqu'un.
aussi loin que getFragmentPosition()
va à peu près getItem()
à l'envers. Vous savez quels fragments vont où, assurez-vous juste de retourner la bonne position qu'il sera. Voici un exemple:
@Override
protected int getFragmentPosition(Fragment fragment) {
if(fragment.equals(originalFragment1)){
return 0;
}
if(fragment.equals(replacementFragment1)){
return 0;
}
if(fragment.equals(Fragment2)){
return 1;
}
return -1;
}
j'ai aussi fait une solution, qui fonctionne avec Stacks . C'est une approche plus modulaire donc nous n'avons pas à spécifier chaque Fragment et Fragment de détail dans votre FragmentPagerAdapter
. Il est construit sur le dessus de l'exemple de ActionbarSherlock qui dérive si j'ai raison de L'application de démonstration de Google.
/**
* This is a helper class that implements the management of tabs and all
* details of connecting a ViewPager with associated TabHost. It relies on a
* trick. Normally a tab host has a simple API for supplying a View or
* Intent that each tab will show. This is not sufficient for switching
* between pages. So instead we make the content part of the tab host
* 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
* view to show as the tab content. It listens to changes in tabs, and takes
* care of switch to the correct paged in the ViewPager whenever the selected
* tab changes.
*
* Changed to support more Layers of fragments on each Tab.
* by sebnapi (2012)
*
*/
public class TabsAdapter extends FragmentPagerAdapter
implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final TabHost mTabHost;
private final ViewPager mViewPager;
private ArrayList<String> mTabTags = new ArrayList<String>();
private HashMap<String, Stack<TabInfo>> mTabStackMap = new HashMap<String, Stack<TabInfo>>();
static final class TabInfo {
public final String tag;
public final Class<?> clss;
public Bundle args;
TabInfo(String _tag, Class<?> _class, Bundle _args) {
tag = _tag;
clss = _class;
args = _args;
}
}
static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
public interface SaveStateBundle{
public Bundle onRemoveFragment(Bundle outState);
}
public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mTabHost = tabHost;
mViewPager = pager;
mTabHost.setOnTabChangedListener(this);
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
}
/**
* Add a Tab which will have Fragment Stack. Add Fragments on this Stack by using
* addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args)
* The Stack will hold always the default Fragment u add here.
*
* DON'T ADD Tabs with same tag, it's not beeing checked and results in unexpected
* beahvior.
*
* @param tabSpec
* @param clss
* @param args
*/
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args){
Stack<TabInfo> tabStack = new Stack<TabInfo>();
tabSpec.setContent(new DummyTabFactory(mContext));
mTabHost.addTab(tabSpec);
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
mTabTags.add(tag); // to know the position of the tab tag
tabStack.add(info);
mTabStackMap.put(tag, tabStack);
notifyDataSetChanged();
}
/**
* Will add the Fragment to Tab with the Tag _tag. Provide the Class of the Fragment
* it will be instantiated by this object. Proivde _args for your Fragment.
*
* @param fm
* @param _tag
* @param _class
* @param _args
*/
public void addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args){
TabInfo info = new TabInfo(_tag, _class, _args);
Stack<TabInfo> tabStack = mTabStackMap.get(_tag);
Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
if(frag instanceof SaveStateBundle){
Bundle b = new Bundle();
((SaveStateBundle) frag).onRemoveFragment(b);
tabStack.peek().args = b;
}
tabStack.add(info);
FragmentTransaction ft = fm.beginTransaction();
ft.remove(frag).commit();
notifyDataSetChanged();
}
/**
* Will pop the Fragment added to the Tab with the Tag _tag
*
* @param fm
* @param _tag
* @return
*/
public boolean popFragment(FragmentManager fm, String _tag){
Stack<TabInfo> tabStack = mTabStackMap.get(_tag);
if(tabStack.size()>1){
tabStack.pop();
Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
FragmentTransaction ft = fm.beginTransaction();
ft.remove(frag).commit();
notifyDataSetChanged();
return true;
}
return false;
}
public boolean back(FragmentManager fm) {
int position = mViewPager.getCurrentItem();
return popFragment(fm, mTabTags.get(position));
}
@Override
public int getCount() {
return mTabStackMap.size();
}
@Override
public int getItemPosition(Object object) {
ArrayList<Class<?>> positionNoneHack = new ArrayList<Class<?>>();
for(Stack<TabInfo> tabStack: mTabStackMap.values()){
positionNoneHack.add(tabStack.peek().clss);
} // if the object class lies on top of our stacks, we return default
if(positionNoneHack.contains(object.getClass())){
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
@Override
public Fragment getItem(int position) {
Stack<TabInfo> tabStack = mTabStackMap.get(mTabTags.get(position));
TabInfo info = tabStack.peek();
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
}
@Override
public void onTabChanged(String tabId) {
int position = mTabHost.getCurrentTab();
mViewPager.setCurrentItem(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
// Unfortunately when TabHost changes the current tab, it kindly
// also takes care of putting focus on it when not in touch mode.
// The jerk.
// This hack tries to prevent this from pulling focus out of our
// ViewPager.
TabWidget widget = mTabHost.getTabWidget();
int oldFocusability = widget.getDescendantFocusability();
widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
mTabHost.setCurrentTab(position);
widget.setDescendantFocusability(oldFocusability);
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
ajoutez ceci pour bouton arrière fonctionnalité dans votre activité principale:
@Override
public void onBackPressed() {
if (!mTabsAdapter.back(getSupportFragmentManager())) {
super.onBackPressed();
}
}
si vous aimez sauvegardez l'État du Fragment quand il est enlevé. Laissez votre Fragment implémenter l'interface SaveStateBundle
retourner dans la fonction un paquet avec votre état de sauvegarde. Obtenez le paquet après instanciation par this.getArguments()
.
Vous pouvez instancier un onglet comme ceci:
mTabsAdapter.addTab(mTabHost.newTabSpec("firstTabTag").setIndicator("First Tab Title"),
FirstFragmentActivity.FirstFragmentFragment.class, null);
fonctionne similiar si vous voulez ajouter un Fragment sur le dessus d'une pile D'onglets. Important : je pense que ça ne marchera pas si vous voulez avoir 2 instances de même classe en plus de deux onglets. J'ai fait cette solution rapidement ensemble, de sorte que je ne peux la partager sans fournir aucune expérience avec elle.
remplacer des fragments dans un viewpager est très impliqué mais est très possible et peut sembler super lisse. Tout d'abord, vous devez laisser le viewpager lui-même gérer la suppression et l'ajout des fragments. Ce qui se passe, c'est que lorsque vous remplacez le fragment à L'intérieur de SearchFragment, votre viewpager conserve ses vues de fragment. Donc vous finissez avec une page blanche parce que le SearchFragment est enlevé quand vous essayez de le remplacer.
la solution est de créer un auditeur à l'intérieur de votre viewpager qui va gérer les changements effectués en dehors de celui-ci donc tout d'abord ajouter ce code au bas de votre adaptateur.
public interface nextFragmentListener {
public void fragment0Changed(String newFragmentIdentification);
}
Ensuite, vous devez créer une classe privée dans votre viewpager qui devient un écouteur pour quand vous voulez changer votre fragment. Par exemple, vous pouvez ajouter quelque chose comme ça. Notez qu'il implémente l'interface qui vient d'être créée. Donc chaque fois que vous appelez cette méthode, elle exécutera le code à l'intérieur de la classe ci-dessous.
private final class fragmentChangeListener implements nextFragmentListener {
@Override
public void fragment0Changed(String fragment) {
//I will explain the purpose of fragment0 in a moment
fragment0 = fragment;
manager.beginTransaction().remove(fragAt0).commit();
switch (fragment){
case "searchFragment":
fragAt0 = SearchFragment.newInstance(listener);
break;
case "searchResultFragment":
fragAt0 = Fragment_Table.newInstance(listener);
break;
}
notifyDataSetChanged();
}
il y a deux choses principales à souligner ici:
- fragAt0 est un fragment" flexible". Il peut prendre n'importe quel type de fragment que vous lui donnez. Cela lui permet de devenir votre meilleur ami en changeant le fragment à la position 0 au fragment que vous désirez.
-
notez les écouteurs qui sont placés dans le 'NewInstance(listener)constructor. Voici comment vous allez appeler fragment0changed (chaîne de caractères) newFragmentIdentification)". Le code suivant montre comment créer l'auditeur à l'intérieur de votre fragment.
static nextFragmentListener listenerSearch;
public static Fragment_Journals newInstance(nextFragmentListener listener){ listenerSearch = listener; return new Fragment_Journals(); }
on pourrait appeler le changement à l'intérieur de votre onPostExecute
private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{
protected Void doInBackground(Void... params){
.
.//some more operation
.
}
protected void onPostExecute(Void param){
listenerSearch.fragment0Changed("searchResultFragment");
}
}
cela déclencherait le code à l'intérieur de votre viewpager pour changer votre fragment à la position zéro fragAt0 pour devenir un nouveau searchResultFragment. Il y a deux plus de petits morceaux que vous auriez besoin d'ajouter au viewpager avant qu'il ne devienne fonctionnel.
L'un serait dans la méthode getItem override du viewpager.
@Override
public Fragment getItem(int index) {
switch (index) {
case 0:
//this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected.
if(fragAt0 == null){
switch(fragment0){
case "searchFragment":
fragAt0 = FragmentSearch.newInstance(listener);
break;
case "searchResultsFragment":
fragAt0 = FragmentSearchResults.newInstance(listener);
break;
}
}
return fragAt0;
case 1:
// Games fragment activity
return new CreateFragment();
}
Maintenant, sans cette dernière pièce, vous obtenez toujours une page blanche. Un peu boiteux, mais c'est une partie essentielle du viewPager. Vous devez outrepasser la méthode getitemportionation du viewpager. Normalement cette méthode retournera POSITION_UNCHANGED qui dit au viewpager de garder tout est pareil et donc getItem ne sera jamais appelé pour placer le nouveau fragment sur la page. Voici un exemple de quelque chose que vous pourriez faire
public int getItemPosition(Object object)
{
//object is the current fragment displayed at position 0.
if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){
return POSITION_NONE;
//this condition is for when you press back
}else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){
return POSITION_NONE;
}
return POSITION_UNCHANGED
}
comme je l'ai dit, le code est très impliqué, mais vous avez 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 pour que je sois patient, mais tout cela aura du sens. Il est tout à fait intéressant de prendre le le temps parce qu'il peut faire une application à la recherche vraiment lisse.
Voici la pépite pour manipuler le bouton arrière. Vous mettez cela dans votre activité principale
public void onBackPressed() {
if(mViewPager.getCurrentItem() == 0) {
if(pagerAdapter.getItem(0) instanceof FragmentSearchResults){
((Fragment_Table) pagerAdapter.getItem(0)).backPressed();
}else if (pagerAdapter.getItem(0) instanceof FragmentSearch) {
finish();
}
}
vous aurez besoin de 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é avant va gérer appuyant sur le bouton arrière. Bonne chance avec votre code pour changer le viewpager. Il faut beaucoup de travail, et dans la mesure comme je l'ai constaté, il n'y a pas d'adaptations rapides. Comme je l'ai dit, vous créez essentiellement un adaptateur de viewpager personnalisé, et le laissez gérer tous les changements nécessaires en utilisant des écouteurs
dans votre méthode onCreateView
, container
est en fait une instance ViewPager
.
donc, j'appelle juste
ViewPager vpViewPager = (ViewPager) container;
vpViewPager.setCurrentItem(1);
va changer le fragment courant dans votre ViewPager
.
voici ma solution relativement simple à ce problème. Les clés de cette solution sont d'utiliser FragmentStatePagerAdapter
au lieu de FragmentPagerAdapter
car le premier supprimera les fragments inutilisés pour vous tandis que le dernier conserve encore leurs instances. La seconde est l'utilisation de POSITION_NONE
dans getItem(). J'ai utilisé une liste simple pour garder la trace de mes fragments. Mon exigence était de remplacer la liste entière des fragments à la fois avec une nouvelle liste, mais le dessous pourrait être facilement modifié pour remplacer individuel fragments:
public class MyFragmentAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragmentList = new ArrayList<Fragment>();
private List<String> tabTitleList = new ArrayList<String>();
public MyFragmentAdapter(FragmentManager fm) {
super(fm);
}
public void addFragments(List<Fragment> fragments, List<String> titles) {
fragmentList.clear();
tabTitleList.clear();
fragmentList.addAll(fragments);
tabTitleList.addAll(titles);
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
if (fragmentList.contains(object)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
@Override
public Fragment getItem(int item) {
if (item >= fragmentList.size()) {
return null;
}
return fragmentList.get(item);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Override
public CharSequence getPageTitle(int position) {
return tabTitleList.get(position);
}
}
je fais quelque chose de similaire à wize mais dans ma réponse vous pouvez changer entre les deux fragments quand vous voulez. Et avec la réponse de wize j'ai quelques problèmes en changeant l'orientation de l'écran et des choses comme ça. C'est le PagerAdapter ressemble à:
public class MyAdapter extends FragmentPagerAdapter
{
static final int NUM_ITEMS = 2;
private final FragmentManager mFragmentManager;
private Fragment mFragmentAtPos0;
private Map<Integer, String> mFragmentTags;
private boolean isNextFragment=false;
public MyAdapter(FragmentManager fm)
{
super(fm);
mFragmentManager = fm;
mFragmentTags = new HashMap<Integer, String>();
}
@Override
public Fragment getItem(int position)
{
if (position == 0)
{
if (isPager) {
mFragmentAtPos0 = new FirstPageFragment();
} else {
mFragmentAtPos0 = new NextFragment();
}
return mFragmentAtPos0;
}
else
return SecondPageFragment.newInstance();
}
@Override
public int getCount()
{
return NUM_ITEMS;
}
@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 void onChange(boolean isNextFragment) {
if (mFragmentAtPos0 == null)
mFragmentAtPos0 = getFragment(0);
if (mFragmentAtPos0 != null)
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
if (!isNextFragment) {
mFragmentAtFlashcards = new FirstPageFragment();
} else {
mFragmentAtFlashcards = new NextFragment();
}
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object)
{
if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
public Fragment getFragment(int position) {
String tag = mFragmentTags.get(position);
if (tag == null)
return null;
return mFragmentManager.findFragmentByTag(tag);
}
}
l'écouteur que j'ai mis en œuvre dans l'activité du conteneur adaptateur pour le mettre au fragment lors de sa fixation, c'est l'activité:
public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {
//...
@Override
public void onChange(boolean isNextFragment) {
if (pagerAdapter != null)
pagerAdapter.onChange(isNextFragment);
}
//...
}
puis dans le fragment mettant l'écouteur lorsque vous attachez un appel:
public class FirstPageFragment extends Fragment{
private ChangeFragmentListener changeFragmentListener;
//...
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
changeFragmentListener = ((PagerContainerActivity) activity);
}
@Override
public void onDetach() {
super.onDetach();
changeFragmentListener = null;
}
//...
//in the on click to change the fragment
changeFragmentListener.onChange(true);
//...
}
et enfin l'auditeur:
public interface changeFragmentListener {
void onChange(boolean isNextFragment);
}
j'ai suivi les réponses par @wize et @mdelolmo et j'ai eu la solution. Merci De Tonnes. Mais, j'ai accordé ces solutions un peu pour améliorer la consommation de mémoire.
Problèmes que j'ai observé:
ils sauvent l'instance de Fragment
qui est remplacée. Dans mon cas, c'est un Fragment qui contient MapView
et j'ai pensé que c'était coûteux. Donc, je maintiens le FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)
à la place de Fragment
lui-même.
Voici mon implémentation.
public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
private SwitchFragListener mSwitchFragListener;
private Switch mToggle;
private int pagerAdapterPosChanged = POSITION_UNCHANGED;
private static final int TOGGLE_ENABLE_POS = 2;
public DemoCollectionPagerAdapter(FragmentManager fm, Switch toggle) {
super(fm);
mToggle = toggle;
mSwitchFragListener = new SwitchFragListener();
mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mSwitchFragListener.onSwitchToNextFragment();
}
});
}
@Override
public Fragment getItem(int i) {
switch (i)
{
case TOGGLE_ENABLE_POS:
if(mToggle.isChecked())
{
return TabReplaceFragment.getInstance();
}else
{
return DemoTab2Fragment.getInstance(i);
}
default:
return DemoTabFragment.getInstance(i);
}
}
@Override
public int getCount() {
return 5;
}
@Override
public CharSequence getPageTitle(int position) {
return "Tab " + (position + 1);
}
@Override
public int getItemPosition(Object object) {
// This check make sures getItem() is called only for the required Fragment
if (object instanceof TabReplaceFragment
|| object instanceof DemoTab2Fragment)
return pagerAdapterPosChanged;
return POSITION_UNCHANGED;
}
/**
* Switch fragments Interface implementation
*/
private final class SwitchFragListener implements
SwitchFragInterface {
SwitchFragListener() {}
public void onSwitchToNextFragment() {
pagerAdapterPosChanged = POSITION_NONE;
notifyDataSetChanged();
}
}
/**
* Interface to switch frags
*/
private interface SwitchFragInterface{
void onSwitchToNextFragment();
}
}
Lien de démonstration ici.. https://youtu.be/l_62uhKkLyM
Pour la démo fin, utilisé 2 fragments TabReplaceFragment
et DemoTab2Fragment
à la position deux. Dans tous les autres cas, j'utilise des instances DemoTabFragment
.
explication:
je passe Switch
de L'activité au DemoCollectionPagerAdapter
. En fonction de l'état de ce commutateur, nous afficherons le fragment correct. Lorsque la vérification de l'interrupteur est modifiée, j'appelle la méthode SwitchFragListener
's onSwitchToNextFragment
, où je change la valeur de la variable pagerAdapterPosChanged
en POSITION_NONE
. En savoir plus sur POSITION_NONE . Cela invalidera le getItem et j'ai la logique pour instancier le bon fragment là-bas. Désolé si l'explication est un peu brouillon.
encore une fois grand grâce à @wize et @mdelolmo pour l'idée originale.
J'espère que c'est utile. :)
faites-moi savoir si cette implémentation a des défauts. Qui sera grandement utile pour mon projet.
j'ai trouvé une solution simple, qui fonctionne très bien même si vous voulez ajouter de nouveaux fragments au milieu ou remplacer fragment actuel. Dans ma solution, vous devez remplacer getItemId()
qui devrait retourner un id unique pour chaque fragment. Pas position en tant que par défaut.
Il est il:
public class DynamicPagerAdapter extends FragmentPagerAdapter {
private ArrayList<Page> mPages = new ArrayList<Page>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
public DynamicPagerAdapter(FragmentManager fm) {
super(fm);
}
public void replacePage(int position, Page page) {
mPages.set(position, page);
notifyDataSetChanged();
}
public void setPages(ArrayList<Page> pages) {
mPages = pages;
notifyDataSetChanged();
}
@Override
public Fragment getItem(int position) {
if (mPages.get(position).mPageType == PageType.FIRST) {
return FirstFragment.newInstance(mPages.get(position));
} else {
return SecondFragment.newInstance(mPages.get(position));
}
}
@Override
public int getCount() {
return mPages.size();
}
@Override
public long getItemId(int position) {
// return unique id
return mPages.get(position).getId();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
while (mFragments.size() <= position) {
mFragments.add(null);
}
mFragments.set(position, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mFragments.set(position, null);
}
@Override
public int getItemPosition(Object object) {
PagerFragment pagerFragment = (PagerFragment) object;
Page page = pagerFragment.getPage();
int position = mFragments.indexOf(pagerFragment);
if (page.equals(mPages.get(position))) {
return POSITION_UNCHANGED;
} else {
return POSITION_NONE;
}
}
}
note: dans cet exemple FirstFragment
et SecondFragment
étend la classe abstraite PageFragment, qui a la méthode getPage()
.
après des recherches j'ai trouvé la solution avec le code court. tout d'abord, créez une instance publique sur fragment et supprimez simplement votre fragment sur onSaveInstanceState si fragment ne recrée pas sur le changement d'orientation.
@Override
public void onSaveInstanceState(Bundle outState) {
if (null != mCalFragment) {
FragmentTransaction bt = getChildFragmentManager().beginTransaction();
bt.remove(mFragment);
bt.commit();
}
super.onSaveInstanceState(outState);
}
C'est ma façon d'y parvenir.
tout d'abord ajouter Root_fragment
à l'intérieur de viewPager
onglet dans lequel vous voulez mettre en œuvre bouton cliquez fragment
événement. Exemple:
@Override
public Fragment getItem(int position) {
if(position==0)
return RootTabFragment.newInstance();
else
return SecondPagerFragment.newInstance();
}
tout d'abord, RootTabFragment
doit inclure FragmentLayout
pour le changement de fragment.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
puis, à l'intérieur de RootTabFragment
onCreateView
, mettre en œuvre fragmentChange
pour votre FirstPagerFragment
getChildFragmentManager().beginTransaction().replace(R.id.root_frame, FirstPagerFragment.newInstance()).commit();
après cela, implémentez onClick
événement pour votre bouton à l'intérieur de FirstPagerFragment
et faites le changement de fragment comme ça à nouveau.
getChildFragmentManager().beginTransaction().replace(R.id.root_frame, NextFragment.newInstance()).commit();
J'espère que ça vous aidera.