ID en double, null nul ou id parent avec un autre fragment pour com.google.android.gms.maps.MapFragment


334

J'ai une application avec trois onglets.

Chaque onglet a son propre fichier de mise en page .xml. Le fichier main.xml possède son propre fragment de carte. C'est celui qui apparaît lors du premier lancement de l'application.

Tout fonctionne bien sauf quand je change entre les onglets. Si j'essaie de revenir à l'onglet de fragment de carte, j'obtiens cette erreur. Passer à et entre d'autres onglets fonctionne très bien.

Qu'est-ce qui pourrait mal ici?

Ceci est ma classe principale et mon main.xml, ainsi qu'une classe pertinente que j'utilise (vous trouverez également le journal des erreurs en bas)

classe principale

package com.nfc.demo;

import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.Toast;

public class NFCDemoActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActionBar bar = getActionBar();
        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);

        bar.addTab(bar
                .newTab()
                .setText("Map")
                .setTabListener(
                        new TabListener<MapFragment>(this, "map",
                                MapFragment.class)));
        bar.addTab(bar
                .newTab()
                .setText("Settings")
                .setTabListener(
                        new TabListener<SettingsFragment>(this, "settings",
                                SettingsFragment.class)));
        bar.addTab(bar
                .newTab()
                .setText("About")
                .setTabListener(
                        new TabListener<AboutFragment>(this, "about",
                                AboutFragment.class)));

        if (savedInstanceState != null) {
            bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
        }
        // setContentView(R.layout.main);

    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
    }

    public static class TabListener<T extends Fragment> implements
            ActionBar.TabListener {
        private final Activity mActivity;
        private final String mTag;
        private final Class<T> mClass;
        private final Bundle mArgs;
        private Fragment mFragment;

        public TabListener(Activity activity, String tag, Class<T> clz) {
            this(activity, tag, clz, null);
        }

        public TabListener(Activity activity, String tag, Class<T> clz,
                Bundle args) {
            mActivity = activity;
            mTag = tag;
            mClass = clz;
            mArgs = args;

            // Check to see if we already have a fragment for this tab,
            // probably from a previously saved state. If so, deactivate
            // it, because our initial state is that a tab isn't shown.
            mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
            if (mFragment != null && !mFragment.isDetached()) {
                FragmentTransaction ft = mActivity.getFragmentManager()
                        .beginTransaction();
                ft.detach(mFragment);
                ft.commit();
            }
        }

        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            if (mFragment == null) {
                mFragment = Fragment.instantiate(mActivity, mClass.getName(),
                        mArgs);
                ft.add(android.R.id.content, mFragment, mTag);
            } else {
                ft.attach(mFragment);
            }
        }

        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
            if (mFragment != null) {
                ft.detach(mFragment);
            }
        }

        public void onTabReselected(Tab tab, FragmentTransaction ft) {
            Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT)
                         .show();
        }
    }

}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapFragment"
        android:name="com.google.android.gms.maps.MapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

classe appropriée (MapFragment.java)

package com.nfc.demo;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MapFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        return inflater.inflate(R.layout.main, container, false);
    }

    public void onDestroy() {
        super.onDestroy();
    }
}

Erreur

android.view.InflateException: Binary XML file line #7: 
     Error inflating class fragment
   at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
   at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
   at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
   at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
   at com.nfc.demo.MapFragment.onCreateView(MapFragment.java:15)
   at android.app.Fragment.performCreateView(Fragment.java:1695)
   at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:885)
   at android.app.FragmentManagerImpl.attachFragment(FragmentManager.java:1255)
   at android.app.BackStackRecord.run(BackStackRecord.java:672)
   at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1435)
   at android.app.FragmentManagerImpl$1.run(FragmentManager.java:441)
   at android.os.Handler.handleCallback(Handler.java:725)
   at android.os.Handler.dispatchMessage(Handler.java:92)
   at android.os.Looper.loop(Looper.java:137)
   at android.app.ActivityThread.main(ActivityThread.java:5039)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:511)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
   at dalvik.system.NativeStart.main(Native Method)

Caused by: java.lang.IllegalArgumentException: 
     Binary XML file line #7: Duplicate id 0x7f040005, tag null, or 
     parent id 0xffffffff with another fragment for 
     com.google.android.gms.maps.MapFragment
   at android.app.Activity.onCreateView(Activity.java:4722)
   at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:680)
   ... 19 more

essayez ceci - retournez super.onCreateView (inflater, container, savedInstanceState); au lieu de super.onCreateView (inflater, container, savedInstanceState); return inflater.inflate (R.layout.main, container, false);
Nik

vous ne devez pas ajouter votre fragment lorsque saveInstanceState n'est pas nul.
muyiou

Jetez un oeil à ce commentaire. Cela pourrait vous aider: stackoverflow.com/questions/15562416/…
Oleksandr

2
Veuillez modifier la réponse acceptée! Vous avez choisi une très mauvaise réponse qui crée des fuites de mémoire! La bonne réponse est la suivante: stackoverflow.com/a/19815266/902276
Daniele Segato

Réponses:


400

La réponse de Matt suggère des travaux, mais cela provoque la recréation et le redessin de la carte, ce qui n'est pas toujours souhaitable. Après de nombreux essais et erreurs, j'ai trouvé une solution qui fonctionne pour moi:

private static View view;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if (view != null) {
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null)
            parent.removeView(view);
    }
    try {
        view = inflater.inflate(R.layout.map, container, false);
    } catch (InflateException e) {
        /* map is already there, just return view as it is */
    }
    return view;
}

Pour faire bonne mesure, voici "map.xml" (R.layout.map) avec R.id.mapFragment (android: id = "@ + id / mapFragment"):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.SupportMapFragment" />
</LinearLayout>

J'espère que cela aide, mais je ne peux pas garantir qu'il n'a pas d'effets négatifs.

Modifier: il y a eu des effets indésirables, comme lors de la fermeture et du redémarrage de l'application. Étant donné que l'application n'est pas nécessairement complètement fermée (mais simplement mise en veille), le code précédent que j'ai soumis échouait au redémarrage de l'application. J'ai mis à jour le code pour quelque chose qui fonctionne pour moi, à la fois entrer et sortir de la carte et quitter et redémarrer l'application, je ne suis pas trop satisfait du bit try-catch, mais il semble fonctionner assez bien. En regardant la trace de la pile, il m'est venu à l'esprit que je pouvais simplement vérifier si le fragment de carte est dans le FragmentManager, pas besoin du bloc try-catch, code mis à jour.

Plus de modifications: il s'avère que vous avez besoin de cet essai après tout. Le simple fait de vérifier le fragment de la carte s'est avéré ne pas fonctionner si bien après tout. Blergh.


25
C'est une mauvaise réponse -1! Vous perdez une activité à l'aide d'un modificateur statique pour une vue. La cause première de ce problème est probablement une autre activité ayant fait l'objet d'une fuite, qui ne peut pas être récupérée car vous conservez une référence forte pointant vers elle. Dans le cas où une InflateException est levée, vous utilisez une vue qui a un contexte d'activité détruite! Mieux vaut trouver d'autres fuites de mémoire dans votre application, cela résoudra tous les problèmes.
tomrozb

1
Je suppose que nous pouvons utiliser WeakReference pour éviter les fuites de mémoire?
Desmond Lua

2
Cela fonctionne aussi pour moi +1. Vous n'êtes pas obligé d'utiliser une référence de vue statique
Karol Żygłowicz

4
Ne fais pas ça !!! Cela provoque une fuite de mémoire. La seule raison pour laquelle cela se produit est que vous gonflez un fragment de XML dans un autre fragment. Vous n'êtes PAS censé faire ça! Vous devez utiliser ChildFragmentManager et ajouter le fragment dans onViewCreated ()!
Daniele Segato

1
Oui, cela fera effectivement fuir l'activité. Trouvé une solution de travail dans stackoverflow.com/a/27592123/683763 . L'idée est de supprimer manuellement la méthode SupportMapFragmentin onDestroyView.
ULazdins

277

Le problème est que ce que vous essayez de faire ne doit pas être fait. Vous ne devriez pas gonfler des fragments à l'intérieur d'autres fragments. De la documentation d'Android :

Remarque: vous ne pouvez pas gonfler une disposition en un fragment lorsque cette disposition comprend un <fragment>. Les fragments imbriqués ne sont pris en charge que lorsqu'ils sont ajoutés à un fragment de manière dynamique.

Bien que vous puissiez être en mesure d'accomplir la tâche avec les hacks présentés ici, je vous suggère fortement de ne pas le faire. Il est impossible d'être sûr que ces hacks géreront ce que fait chaque nouveau système d'exploitation Android lorsque vous essayez de gonfler une disposition pour un fragment contenant un autre fragment.

La seule façon prise en charge par Android pour ajouter un fragment à un autre fragment est via une transaction à partir du gestionnaire de fragments enfants.

Changez simplement votre disposition XML dans un conteneur vide (ajoutez un ID si nécessaire):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapFragmentContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
</LinearLayout>

Puis dans la onViewCreated(View view, @Nullable Bundle savedInstanceState)méthode Fragment :

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    FragmentManager fm = getChildFragmentManager();
    SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentByTag("mapFragment");
    if (mapFragment == null) {
        mapFragment = new SupportMapFragment();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.mapFragmentContainer, mapFragment, "mapFragment");
        ft.commit();
        fm.executePendingTransactions();
    }
    mapFragment.getMapAsync(callback);
}

4
Voir stackoverflow.com/questions/13733299/… pour des exemples de création d'un fragment de carte par programmation et d'initialisation de la carte.
Kristopher Johnson

1
Voir également stackoverflow.com/questions/19239175/… pour une solution de contournement concernant un bogue apparent dans la prise en charge des fragments enfants.
Kristopher Johnson

J'ai rencontré ce problème 4.3lors de l'utilisation d'un SupportMapFragmentdéfini en XML. La création dynamique du fragment et son injection dans une vue de conteneur ont résolu le problème. Voir cette réponse SO .
theblang

Réponse très utile. Comment pouvons-nous faire un rappel à la fonction principale onCreate ()?
Utopia

2
Selon le doc, utilisez SupportMapFragment.newInstance(); developers.google.com/maps/documentation/android-api/map
Jemshit Iskenderov

178

J'ai eu le même problème et j'ai pu le résoudre en supprimant manuellement le MapFragmentdans la onDestroy()méthode de la Fragmentclasse. Voici le code qui fonctionne et fait référence à l' MapFragmentID par dans le XML:

@Override
public void onDestroyView() {
    super.onDestroyView();
    MapFragment f = (MapFragment) getFragmentManager()
                                         .findFragmentById(R.id.map);
    if (f != null) 
        getFragmentManager().beginTransaction().remove(f).commit();
}

Si vous ne supprimez pas MapFragmentmanuellement, il restera en place de sorte qu'il ne coûte pas beaucoup de ressources pour recréer / afficher à nouveau la vue de la carte. Il semble que conserver le sous MapView- jacent soit idéal pour basculer entre les onglets, mais lorsqu'il est utilisé dans des fragments, ce comportement provoque la MapViewcréation d' un doublon à chaque nouveau MapFragmentavec le même ID. La solution consiste à supprimer manuellement le MapFragmentet donc à recréer la carte sous-jacente à chaque fois que le fragment est gonflé.

J'ai également noté cela dans une autre réponse [ 1 ].


Il fait planter l'application en cliquant sur le bouton d'accueil de l'appareil, avec le "Ne pas garder les activités" dans les options du développeur.
jul

24
cela fonctionne, mais si je fais pivoter mon écran, mon application se bloque avec cette exception: causée par: java.lang.IllegalStateException: ne peut pas effectuer cette action après onSaveInstanceState
jramoyo

1
Pour ceux qui pourraient être intéressés, vous pouvez éliminer l'exception causée par la rotation de l'écran en stockant l'orientation initiale dans le constructeur du fragment et en appelant la transaction ci-dessus uniquement si l'orientation n'a PAS été modifiée. S'il a été modifié, il n'est pas nécessaire de lutter contre l'ID en double, car il n'est pas là lorsque la vue est créée à nouveau après la rotation. Je peux modifier la réponse et fournir un extrait de code, si cela ne me dérange pas.
Stan

1
Hmm pour moi, le fragment de carte n'est pas supprimé. Je dois faire quelque chose de mal mais si j'appelle getActivity (). GetSupportFragmentManager (). GetFragments (), le fragment reste après l'appel à remove (f) .commit. Quelqu'un sait-il pourquoi? (J'ai remplacé getFragManager par getSupportFragManager)
Daniel Wilson

10
commitAllowingStateLoss évitera l'exception IllegalStateException.
Héctor Júdez Sapena

22

Voici ma réponse:

1, créez un xml de mise en page comme suit:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>

2, dans la classe Fragment, ajoutez une carte Google par programmation.

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * A simple {@link android.support.v4.app.Fragment} subclass. Activities that
 * contain this fragment must implement the
 * {@link MapFragment.OnFragmentInteractionListener} interface to handle
 * interaction events. Use the {@link MapFragment#newInstance} factory method to
 * create an instance of this fragment.
 * 
 */
public class MapFragment extends Fragment {
    // TODO: Rename parameter arguments, choose names that match
    private GoogleMap mMap;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_map, container, false);
        SupportMapFragment mMapFragment = SupportMapFragment.newInstance();
        mMap = mMapFragment.getMap();
        FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
        transaction.add(R.id.map_container, mMapFragment).commit();
        return view;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        Log.d("Attach", "on attach");
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }
} 

2
mMapFragment.getMap();retourne null. Une idée pourquoi?
Anas Azeem

@AnasAzeem, la création d'un fragment par programme résout exactement le problème, vous n'avez pas besoin d'obtenir mMap pour la solution, peut-être dans votre cas.
yesildal

@AnasAzeem, vous devez utiliser get getMapAsync qui retournera une instance de carte correctement après l'initialisation (en arrière-plan). C'est la "bonne" façon de travailler avec Google Maps et n'a rien à voir avec les fragments
Ganesh Krishnan

10
  1. Comme mentionné par @Justin Breitfeller, la solution @Vidar Wahlberg est un hack qui pourrait ne pas fonctionner dans la future version d'Android.
  2. @Vidar Wahlberg a effectué un piratage, car une autre solution pourrait "provoquer la recréation et le redessinage de la carte, ce qui n'est pas toujours souhaitable". Le redessin de la carte pourrait être évité en conservant l'ancien fragment de carte, plutôt qu'en créant à chaque fois une nouvelle instance.
  3. La solution @Matt ne fonctionne pas pour moi (IllegalStateException)
  4. Comme cité par @Justin Breitfeller, "Vous ne pouvez pas gonfler une disposition dans un fragment lorsque cette disposition comprend un. Les fragments imbriqués ne sont pris en charge que lorsqu'ils sont ajoutés à un fragment dynamiquement."

Ma solution:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,                              Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_map_list, container, false);

    // init
    //mapFragment = (SupportMapFragment)getChildFragmentManager().findFragmentById(R.id.map);
    // don't recreate fragment everytime ensure last map location/state are maintain
    if (mapFragment == null) {
        mapFragment = SupportMapFragment.newInstance();
        mapFragment.getMapAsync(this);
    }
    FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
    // R.id.map is a layout
    transaction.replace(R.id.map, mapFragment).commit();

    return view;
}

2
Merci @Desmond Votre solution a parfaitement fonctionné pour moi. La seule chose dont vous devez vous souvenir est de NE PAS créer la carte dans la mise en page. La création de cartes dans cette solution est intégrée dans le code, donc changez <fragment android: name = "com.google.android.gms.maps.SupportMapFragment"> par exemple <LinearLayout id = "@ + id / map />
kosiara - Bartosz Kosarzycki

7

Déclarer globalement l'objet SupportMapFragment

    private SupportMapFragment mapFragment;

Dans la méthode onCreateView (), mettre sous le code

mapFragment = (SupportMapFragment) getChildFragmentManager()
            .findFragmentById(R.id.map);
 mapFragment.getMapAsync(this);

Dans onDestroyView () mettre en dessous du code

@Override
public void onDestroyView() {
   super.onDestroyView();

    if (mapFragment != null)
        getFragmentManager().beginTransaction().remove(mapFragment).commit();
}

Dans votre fichier xml mettez le code ci-dessous

 <fragment
    android:id="@+id/map"
    android:name="com.abc.Driver.fragment.FragmentHome"
    class="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

Le code ci-dessus a résolu mon problème et cela fonctionne bien


3

Je recommanderais replace()plutôt que attach()/ detach()dans votre gestion des onglets.

Ou passez à ViewPager. Voici un exemple de projet montrant un ViewPager, avec des onglets, hébergeant 10 cartes.


Mais remplacez donner une erreur outofmemry que ce qui devrait faire si nous utilisons la fenêtre de détachement qu'un problème de carte se produit.
Développeur Vishal Android

3

Une autre solution:

if (view == null) {
    view = inflater.inflate(R.layout.nearbyplaces, container, false);
}

Ça y est, sinon null vous n'avez pas besoin de le réinitialiser, la suppression du parent est une étape inutile.


C'est la meilleure solution pour mon cas. J'obtenais cette erreur à la suite de l'utilisation de deux fragments pour mon graphique de navigation et cela a résolu mon problème.
Kennedy Kambo

C'est la meilleure solution pour mon cas. J'obtenais cette erreur à la suite de l'utilisation de deux fragments pour mon graphique de navigation et cela a résolu mon problème.
Kennedy Kambo

2

J'ai perdu des heures aujourd'hui pour trouver la raison, heureusement, ce problème n'est pas dû à l'implémentation de MapFragment, malheureusement, cela ne fonctionne pas car les fragments imbriqués ne sont pris en charge que via la bibliothèque de support à partir de la version 11.

Mon implémentation a une activité avec barre d'action (en mode onglet) avec deux onglets (pas de viewpager), l'un ayant la carte et l'autre ayant une liste d'entrées. Bien sûr, j'ai été assez naïf pour utiliser MapFragment dans mes fragments d'onglet, et voila l'application s'est écrasée à chaque fois que je revenais sur map-tab.

(Le même problème que j'aurais également au cas où mon fragment de tabulation gonflerait toute disposition contenant un autre fragment).

Une option consiste à utiliser MapView (au lieu de MapFragment), avec quelques frais généraux cependant (voir les documents MapView comme remplacement de remplacement dans le layout.xml, une autre option consiste à utiliser la bibliothèque de support à partir de la version 11, mais ensuite à adopter une approche programmatique puisque les fragments imbriqués ne sont ni pris en charge via la mise en page. Ou tout simplement contourner par programmation en détruisant explicitement le fragment (comme dans la réponse de Matt / Vidar), btw: le même effet est obtenu en utilisant MapView (option 1).

Mais en fait, je ne voulais pas perdre la carte chaque fois que je tabule, c'est-à-dire que je voulais la garder en mémoire et nettoyer uniquement lors de la fermeture de l'activité, j'ai donc décidé de simplement masquer / afficher la carte pendant la tabulation, voir FragmentTransaction / hide


2

Pour ceux qui rencontrent toujours ce problème, la meilleure façon de vous assurer de ne pas obtenir cette erreur avec une carte dans un onglet consiste à étendre le fragment SupportMapFragmentau lieu d'imbriquer un SupportMapFragmentà l'intérieur du fragment utilisé pour l'onglet.

Je viens de faire fonctionner cela en utilisant un ViewPageravec un FragmentPagerAdapter, avec le SupportMapFragment dans le troisième onglet.

Voici la structure générale, notez qu'il n'est pas nécessaire de remplacer la onCreateView()méthode, et il n'est pas nécessaire de gonfler le XML de mise en page:

public class MapTabFragment extends SupportMapFragment 
                                    implements OnMapReadyCallback {

    private GoogleMap mMap;
    private Marker marker;


    public MapTabFragment() {
    }

    @Override
    public void onResume() {
        super.onResume();

        setUpMapIfNeeded();
    }

    private void setUpMapIfNeeded() {

        if (mMap == null) {

            getMapAsync(this);
        }
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {

        mMap = googleMap;
        setUpMap();
    }

    private void setUpMap() {

        mMap.setMyLocationEnabled(true);
        mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
        mMap.getUiSettings().setMapToolbarEnabled(false);


        mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

            @Override
            public void onMapClick(LatLng point) {

                //remove previously placed Marker
                if (marker != null) {
                    marker.remove();
                }

                //place marker where user just clicked
                marker = mMap.addMarker(new MarkerOptions().position(point).title("Marker")
                        .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));

            }
        });

    }


}

Résultat:

entrez la description de l'image ici

Voici le code de classe complet que j'ai utilisé pour tester, qui comprend le fragment d'espace réservé utilisé pour les deux premiers onglets et le fragment de carte utilisé pour le troisième onglet:

public class MainActivity extends AppCompatActivity implements ActionBar.TabListener{


    SectionsPagerAdapter mSectionsPagerAdapter;

    ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

        // Set up the ViewPager with the sections adapter.
        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mSectionsPagerAdapter);

        final ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                actionBar.setSelectedNavigationItem(position);
            }
        });

        for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
            actionBar.addTab(actionBar.newTab().setText(mSectionsPagerAdapter.getPageTitle(i)).setTabListener(this));
        }

    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
        mViewPager.setCurrentItem(tab.getPosition());
    }

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {

    }

    @Override
    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {

    }


    public class SectionsPagerAdapter extends FragmentPagerAdapter {

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {

            switch (position) {
                case 0:
                    return PlaceholderFragment.newInstance(position + 1);
                case 1:
                    return PlaceholderFragment.newInstance(position + 1);
                case 2:
                    return MapTabFragment.newInstance(position + 1);
            }

            return null;
        }

        @Override
        public int getCount() {
            // Show 3 total pages.
            return 3;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            Locale l = Locale.getDefault();

            switch (position) {
                case 0:
                    return getString(R.string.title_section1).toUpperCase(l);
                case 1:
                    return getString(R.string.title_section2).toUpperCase(l);
                case 2:
                    return getString(R.string.title_section3).toUpperCase(l);
            }
            return null;
        }
    }


    public static class PlaceholderFragment extends Fragment {

        private static final String ARG_SECTION_NUMBER = "section_number";

        TextView text;

        public static PlaceholderFragment newInstance(int sectionNumber) {
            PlaceholderFragment fragment = new PlaceholderFragment();
            Bundle args = new Bundle();
            args.putInt(ARG_SECTION_NUMBER, sectionNumber);
            fragment.setArguments(args);
            return fragment;
        }

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            text = (TextView) rootView.findViewById(R.id.section_label);
            text.setText("placeholder");

            return rootView;
        }
    }

    public static class MapTabFragment extends SupportMapFragment implements
            OnMapReadyCallback {

        private static final String ARG_SECTION_NUMBER = "section_number";

        private GoogleMap mMap;
        private Marker marker;


        public static MapTabFragment newInstance(int sectionNumber) {
            MapTabFragment fragment = new MapTabFragment();
            Bundle args = new Bundle();
            args.putInt(ARG_SECTION_NUMBER, sectionNumber);
            fragment.setArguments(args);
            return fragment;
        }

        public MapTabFragment() {
        }

        @Override
        public void onResume() {
            super.onResume();

            Log.d("MyMap", "onResume");
            setUpMapIfNeeded();
        }

        private void setUpMapIfNeeded() {

            if (mMap == null) {

                Log.d("MyMap", "setUpMapIfNeeded");

                getMapAsync(this);
            }
        }

        @Override
        public void onMapReady(GoogleMap googleMap) {
            Log.d("MyMap", "onMapReady");
            mMap = googleMap;
            setUpMap();
        }

        private void setUpMap() {

            mMap.setMyLocationEnabled(true);
            mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
            mMap.getUiSettings().setMapToolbarEnabled(false);


            mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

                @Override
                public void onMapClick(LatLng point) {

                    Log.d("MyMap", "MapClick");

                    //remove previously placed Marker
                    if (marker != null) {
                        marker.remove();
                    }

                    //place marker where user just clicked
                    marker = mMap.addMarker(new MarkerOptions().position(point).title("Marker")
                            .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));

                    Log.d("MyMap", "MapClick After Add Marker");

                }
            });

        }
    }
}

2

Je respecte toutes les réponses mais j'ai trouvé cette solution de liner: Si n est le nombre d'onglets alors:

 mViewPager.setOffscreenPageLimit(n);

Exemple: Dans le cas mentionné:

 mViewPager.setOffscreenPageLimit(2);

Le pager de vue implémente une file d'attente, vous n'avez donc pas à le laisser supprimer ce fragment. onCreateView n'est appelé qu'une seule fois.


1

Vous retournez ou gonflez la disposition deux fois, vérifiez simplement si vous ne gonflez qu'une seule fois.



0

Avez-vous essayé de référencer votre MapFragmentclasse personnalisée dans le fichier de disposition?

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapFragment"
        android:name="com.nfc.demo.MapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

pourriez-vous s'il vous plaît poster le code du fragment de carte personnalisé com.nfc.demo.MapFragment
Mina Fawzy

Je ne suis pas l'auteur du code - je viens d'utiliser le code affiché en question. Vous devez demander à Hermann.
JJD

0

Si vous n'utilisez que la réponse Vidar Wahlberg, vous obtenez une erreur lorsque vous ouvrez une autre activité (par exemple) et revenez à la carte. Ou dans mon cas, ouvrez une autre activité, puis à partir d'une nouvelle activité, ouvrez à nouveau la carte (sans utiliser le bouton de retour). Mais lorsque vous combinez la solution Vidar Wahlberg et la solution Matt, vous n'aurez pas d'exceptions.

disposition

<com.example.ui.layout.MapWrapperLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/map_relative_layout">

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/root">

        <fragment xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/map"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            class="com.google.android.gms.maps.SupportMapFragment" />
    </RelativeLayout>
</<com.example.ui.layout.MapWrapperLayout>

Fragment

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    setHasOptionsMenu(true);
    if (view != null) {
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null){
            parent.removeView(view);
        }
    }
    try {
        view = inflater.inflate(R.layout.map_view, null);
        if(view!=null){
            ViewGroup root = (ViewGroup) view.findViewById(R.id.root);
...

@Override
public void onDestroyView() {
    super.onDestroyView();
    Fragment fragment = this.getSherlockActivity().getSupportFragmentManager().findFragmentById(R.id.map);
    if (fragment != null)
        getFragmentManager().beginTransaction().remove(fragment).commit();
}

0

Je l'avais dans viewPager et le crash était dû au fait que tout fragment devait avoir sa propre balise, les balises en double ou les identifiants pour le même fragment ne sont pas autorisés.


0

Je pense qu'il y avait quelques bugs dans la bibliothèque App-Compat précédente pour Fragment enfant. J'ai essayé @Vidar Wahlberg et @ Matt's et ils ne fonctionnaient pas pour moi. Après avoir mis à jour la bibliothèque appcompat, mon code fonctionne parfaitement sans effort supplémentaire.


0

Ce qu'il faut noter ici, c'est que votre application plantera gravement dans l'un des deux cas suivants: -

1) Afin de réutiliser à nouveau le fragment avec Maps, MapView Fragment doit être supprimé lorsque votre fragment montrant Maps a été remplacé par un autre fragment dans le rappel onDestroyView.

sinon, lorsque vous essayez de gonfler deux fois le même fragment , l'ID en double, la balise null ou l'ID parent avec un autre fragment pour l' erreur com.google.android.gms.maps.MapFragment se produira.

2) Deuxièmement, vous ne devez pas mélanger les opérations app.Fragment avec les opérations d'API android.support.v4.app.Fragment, par exemple, n'utilisez pas android.app.FragmentTransaction pour supprimer le type MapView Fragment de type v4.app.Fragment. Mélanger cela entraînera à nouveau un crash du côté du fragment.

Voici un exemple d'extrait de code pour une utilisation correcte de MapView

import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapClickListener;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.serveroverload.yago.R;

/**
 * @author 663918
 *
 */
public class HomeFragment extends Fragment implements LocationListener {
    // Class to do operations on the Map
    GoogleMap googleMap;
    private LocationManager locationManager;

    public static Fragment newInstance() {
        return new HomeFragment();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.home_fragment, container, false);
        Bundle bdl = getArguments();

        // setuping locatiomanager to perfrom location related operations
        locationManager = (LocationManager) getActivity().getSystemService(
                Context.LOCATION_SERVICE);

        // Requesting locationmanager for location updates
        locationManager.requestLocationUpdates(
                LocationManager.NETWORK_PROVIDER, 1, 1, this);

        // To get map from MapFragment from layout
        googleMap = ((MapFragment) getActivity().getFragmentManager()
                .findFragmentById(R.id.map)).getMap();

        // To change the map type to Satellite
        // googleMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);

        // To show our current location in the map with dot
        // googleMap.setMyLocationEnabled(true);

        // To listen action whenever we click on the map
        googleMap.setOnMapClickListener(new OnMapClickListener() {

            @Override
            public void onMapClick(LatLng latLng) {
                /*
                 * LatLng:Class will give us selected position lattigude and
                 * longitude values
                 */
                Toast.makeText(getActivity(), latLng.toString(),
                        Toast.LENGTH_LONG).show();
            }
        });

        changeMapMode(2);

        // googleMap.setSatellite(true);
        googleMap.setTrafficEnabled(true);
        googleMap.setBuildingsEnabled(true);
        googleMap.setMyLocationEnabled(true);

        return v;
    }

    private void doZoom() {
        if (googleMap != null) {
            googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                    new LatLng(18.520430, 73.856744), 17));
        }
    }

    private void changeMapMode(int mapMode) {

        if (googleMap != null) {
            switch (mapMode) {
            case 0:
                googleMap.setMapType(GoogleMap.MAP_TYPE_NONE);
                break;

            case 1:
                googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
                break;

            case 2:
                googleMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
                break;

            case 3:
                googleMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
                break;

            case 4:
                googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
                break;

            default:
                break;
            }
        }
    }

    private void createMarker(double latitude, double longitude) {
        // double latitude = 17.385044;
        // double longitude = 78.486671;

        // lets place some 10 random markers
        for (int i = 0; i < 10; i++) {
            // random latitude and logitude
            double[] randomLocation = createRandLocation(latitude, longitude);

            // Adding a marker
            MarkerOptions marker = new MarkerOptions().position(
                    new LatLng(randomLocation[0], randomLocation[1])).title(
                    "Hello Maps " + i);

            Log.e("Random", "> " + randomLocation[0] + ", " + randomLocation[1]);

            // changing marker color
            if (i == 0)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_AZURE));
            if (i == 1)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_BLUE));
            if (i == 2)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_CYAN));
            if (i == 3)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_GREEN));
            if (i == 4)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA));
            if (i == 5)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_ORANGE));
            if (i == 6)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_RED));
            if (i == 7)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_ROSE));
            if (i == 8)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_VIOLET));
            if (i == 9)
                marker.icon(BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_YELLOW));

            googleMap.addMarker(marker);

            // Move the camera to last position with a zoom level
            if (i == 9) {
                CameraPosition cameraPosition = new CameraPosition.Builder()
                        .target(new LatLng(randomLocation[0], randomLocation[1]))
                        .zoom(15).build();

                googleMap.animateCamera(CameraUpdateFactory
                        .newCameraPosition(cameraPosition));
            }
        }

    }

    /*
     * creating random postion around a location for testing purpose only
     */
    private double[] createRandLocation(double latitude, double longitude) {

        return new double[] { latitude + ((Math.random() - 0.5) / 500),
                longitude + ((Math.random() - 0.5) / 500),
                150 + ((Math.random() - 0.5) * 10) };
    }

    @Override
    public void onLocationChanged(Location location) {

        if (null != googleMap) {
            // To get lattitude value from location object
            double latti = location.getLatitude();
            // To get longitude value from location object
            double longi = location.getLongitude();

            // To hold lattitude and longitude values
            LatLng position = new LatLng(latti, longi);

            createMarker(latti, longi);

            // Creating object to pass our current location to the map
            MarkerOptions markerOptions = new MarkerOptions();
            // To store current location in the markeroptions object
            markerOptions.position(position);

            // Zooming to our current location with zoom level 17.0f
            googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(position,
                    17f));

            // adding markeroptions class object to the map to show our current
            // location in the map with help of default marker
            googleMap.addMarker(markerOptions);
        }

    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onProviderEnabled(String provider) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onProviderDisabled(String provider) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onDestroyView() {
        // TODO Auto-generated method stub
        super.onDestroyView();

        locationManager.removeUpdates(this);

        android.app.Fragment fragment = getActivity().getFragmentManager()
                .findFragmentById(R.id.map);
        if (null != fragment) {
            android.app.FragmentTransaction ft = getActivity()
                    .getFragmentManager().beginTransaction();
            ft.remove(fragment);
            ft.commit();
        }
    }

}

XML

 <fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.MapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
       />

Le résultat ressemble à ceci: -entrez la description de l'image ici

J'espère que cela aidera quelqu'un.


0

Dans cette solution, vous n'avez pas besoin de prendre de variable statique;

Button nextBtn;

private SupportMapFragment mMapFragment;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);

    if (mRootView != null) {
        ViewGroup parent = (ViewGroup) mRootView.getParent();
        Utility.log(0,"removeView","mRootView not NULL");
        if (parent != null) {
            Utility.log(0, "removeView", "view removeViewed");
            parent.removeAllViews();
        }
    }
    else {
        try {
            mRootView = inflater.inflate(R.layout.dummy_fragment_layout_one, container, false);//
        } catch (InflateException e) {
    /* map is already there, just return view as it is  */
            e.printStackTrace();
        }
    }

    return  mRootView;
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    FragmentManager fm = getChildFragmentManager();
    SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentById(R.id.mapView);
    if (mapFragment == null) {
        mapFragment = new SupportMapFragment();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.mapView, mapFragment, "mapFragment");
        ft.commit();
        fm.executePendingTransactions();
    }
    //mapFragment.getMapAsync(this);
    nextBtn = (Button) view.findViewById(R.id.nextBtn);
    nextBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Utility.replaceSupportFragment(getActivity(),R.id.dummyFragment,dummyFragment_2.class.getSimpleName(),null,new dummyFragment_2());
        }
    });

}`

0

Je suis un peu en retard à la fête mais aucune de ces réponses ne m'a aidé dans mon cas. J'utilisais Google map comme SupportMapFragment et PlaceAutocompleteFragment dans mon fragment. Comme toutes les réponses ont souligné le fait que le problème est que SupportMapFragment est la carte à recréer et à redessiner, mais après avoir creusé, mon problème était en fait avec PlaceAutocompleteFragment

Voici donc la solution de travail pour ceux qui sont confrontés à ce problème en raison de SupportMapFragment et SupportMapFragment

 //Global SupportMapFragment mapFragment;
 mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapFragment);
    FragmentManager fm = getChildFragmentManager();

    if (mapFragment == null) {
        mapFragment = SupportMapFragment.newInstance();
        fm.beginTransaction().replace(R.id.mapFragment, mapFragment).commit();
        fm.executePendingTransactions();
    }

    mapFragment.getMapAsync(this);

    //Global PlaceAutocompleteFragment autocompleteFragment;


    if (autocompleteFragment == null) {
        autocompleteFragment = (PlaceAutocompleteFragment) getActivity().getFragmentManager().findFragmentById(R.id.place_autoCompleteFragment);

    }

Et dans onDestroyView, effacez SupportMapFragment et SupportMapFragment

@Override
public void onDestroyView() {
    super.onDestroyView();


    if (getActivity() != null) {
        Log.e("res","place dlted");
        android.app.FragmentManager fragmentManager = getActivity().getFragmentManager();
        android.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.remove(autocompleteFragment);
        fragmentTransaction.commit(); 
       //Use commitAllowingStateLoss() if getting exception 

        autocompleteFragment = null;
    }


}

0

Essayez de définir un identifiant (android: id = "@ + id / maps_dialog") pour la disposition parent mapView. Travaille pour moi.


0

Quiconque vient ici maintenant qui obtient ce type d'erreur lors de l'ouverture de l'un Dialogou l'autre Fragmentavec Google Places AutocompleteSupportFragment, essayez ce one-liner (je ne sais pas à quel point c'est sûr mais cela fonctionne pour moi):

autocompleteFragment.getFragmentManager().beginTransaction().remove(autocompleteFragment).commit();

avant de rejeter / détruire votre fragment.


-1
<?xml version="1.0" encoding="utf-8"?>

  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
   android:layout_height="match_parent" >


<com.google.android.gms.maps.MapView
    android:id="@+id/mapview"
    android:layout_width="100dip"
    android:layout_height="100dip"
    android:layout_alignParentTop="true"
    android:layout_alignRight="@+id/textView1"
    android:layout_marginRight="15dp" >
</com.google.android.gms.maps.MapView>

Pourquoi n'insérez-vous pas une carte à l'aide de l'objet MapView au lieu de MapFragment? Je ne sais pas s'il y a une limitation dans MapView, bien que je l'ai trouvée utile.


Désolé, j'ai appris qu'il s'agit de la version précédente obsolète de l'API Google Maps
Ankur Gautam
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.