ID duplicato, tag null o ID principale con un altro frammento per com.google.android.gms.maps.MapFragment


334

Ho un'applicazione con tre schede.

Ogni scheda ha il proprio file .xml di layout. Main.xml ha il suo frammento di mappa. È quello che appare al primo avvio dell'applicazione.

Tutto funziona bene, tranne quando cambio tra le schede. Se provo a tornare alla scheda del frammento della mappa, visualizzo questo errore. Passare ae tra le altre schede funziona bene.

Cosa potrebbe essere sbagliato qui?

Questa è la mia classe principale e il mio main.xml, oltre a una classe pertinente che uso (troverai anche il registro degli errori in fondo)

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

errore

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

prova questo: restituisci super.onCreateView (inflater, container, savedInstanceState); anziché super.onCreateView (inflater, container, savedInstanceState); return inflater.inflate (R.layout.main, container, false);
Nik

non devi aggiungere il frammento quando saveInstanceState non è null.
muyiou,

Dai un'occhiata a questo commento. Potrebbe esserti utile: stackoverflow.com/questions/15562416/…
Oleksandr

2
Per favore cambia la risposta accettata! Hai scelto una pessima risposta che crea perdite di memoria! La risposta corretta è questa: stackoverflow.com/a/19815266/902276
Daniele Segato

Risposte:


400

La risposta che Matt suggerisce funziona, ma fa sì che la mappa venga ricreata e ridisegnata, il che non è sempre desiderabile. Dopo molte prove ed errori, ho trovato una soluzione che funziona per me:

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

Per una buona misura, ecco "map.xml" (R.layout.map) con 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>

Spero che questo aiuti, ma non posso garantire che non abbia effetti negativi.

Modifica: si sono verificati alcuni effetti negativi, ad esempio quando si esce dall'applicazione e si riavvia. Dal momento che l'applicazione non è necessariamente completamente chiusa (ma solo messa in sospensione in background), il codice precedente che ho inviato fallirebbe al riavvio dell'applicazione. Ho aggiornato il codice con qualcosa che funziona per me, sia entrando e uscendo dalla mappa, sia uscendo e riavviando l'applicazione, non sono troppo contento del bit try-catch, ma sembra funzionare abbastanza bene. Guardando la traccia dello stack mi è venuto in mente che potevo semplicemente verificare se il frammento della mappa fosse nel FragmentManager, senza bisogno del blocco try-catch, il codice è stato aggiornato.

Altre modifiche: dopo tutto, hai bisogno di provare a catturare. Il solo controllo del frammento della mappa si è rivelato non funzionare così bene dopo tutto. Blergh.


25
Questa è una risposta sbagliata -1! Stai perdendo un'attività utilizzando il modificatore statico per una vista. La causa principale di questo problema è probabilmente un'altra attività trapelata, che non può essere raccolta in modo errato perché stai mantenendo un riferimento forte che lo indica. Nel caso in cui venga generata un'InflateException, stai usando una vista che ha un contesto di attività distrutta! Meglio trovare altre perdite di memoria nella tua app, questo risolverà tutti i problemi.
tomrozb,

1
Immagino che possiamo usare WeakReference per evitare perdite di memoria?
Desmond Lua,

2
Questo funziona anche per me +1. Non sei obbligato a utilizzare il riferimento statico della vista
Karol Żygłowicz,

4
Non farlo !!! Causa perdita di memoria. L'unica ragione per cui ciò accade è perché stai gonfiando un frammento da XML all'interno di un altro frammento. NON dovresti farlo! Dovresti usare ChildFragmentManager e aggiungere il frammento in onViewCreated ()!
Daniele Segato,

1
Sì, questo effettivamente perderà l'attività. Trovata una soluzione funzionante in stackoverflow.com/a/27592123/683763 . L'idea è quella di rimuovere manualmente la SupportMapFragmenta onDestroyViewmetodo.
ULazdins,

277

Il problema è che ciò che stai cercando di fare non dovrebbe essere fatto. Non dovresti gonfiare frammenti all'interno di altri frammenti. Dalla documentazione di Android :

Nota: non è possibile gonfiare un layout in un frammento quando tale layout include un <frammento>. I frammenti nidificati sono supportati solo se aggiunti a un frammento in modo dinamico.

Mentre potresti essere in grado di svolgere il compito con gli hack presentati qui, ti consiglio vivamente di non farlo. È impossibile essere sicuri che questi hack gestiranno ciò che fa ogni nuovo sistema operativo Android quando si tenta di gonfiare un layout per un frammento contenente un altro frammento.

L'unico modo supportato da Android per aggiungere un frammento a un altro frammento è tramite una transazione dal gestore di frammenti figlio.

Basta cambiare il layout XML in un contenitore vuoto (aggiungere un ID se necessario):

<?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>

Quindi nel onViewCreated(View view, @Nullable Bundle savedInstanceState)metodo Frammento :

@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
Vedere stackoverflow.com/questions/13733299/… per esempi su come creare un frammento di mappa a livello di codice e inizializzare la mappa.
Kristopher Johnson,

1
Vedere anche stackoverflow.com/questions/19239175/… per soluzioni alternative per bug apparenti nel supporto dei frammenti secondari.
Kristopher Johnson,

Ho riscontrato questo problema 4.3durante l'utilizzo di un SupportMapFragmentdefinito in XML. La creazione dinamica del frammento e l'iniezione in una vista contenitore ha risolto il problema. Vedi questa risposta SO .
theblang,

Risposta molto utile Come possiamo richiamare la funzione principale onCreate ()?
Utopia,

2
Secondo il documento, usa SupportMapFragment.newInstance(); developers.google.com/maps/documentation/android-api/map
Jemshit Iskenderov,

178

Ho avuto lo stesso problema e sono stato in grado di risolverlo rimuovendo manualmente MapFragmentil onDestroy()metodo nel corso della Fragmentclasse. Ecco il codice che funziona e fa riferimento MapFragmentall'ID per nell'XML:

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

Se non lo rimuovi MapFragmentmanualmente, si bloccherà in modo da non dover ricorrere a molte risorse per ricreare / mostrare di nuovo la visualizzazione della mappa. Sembra che mantenere il sottostante MapViewsia ottimo per passare da una scheda all'altra, ma se usato in frammenti questo comportamento provoca la MapViewcreazione di un duplicato su ogni nuovo MapFragmentcon lo stesso ID. La soluzione è rimuovere manualmente MapFragmente quindi ricreare la mappa sottostante ogni volta che il frammento viene gonfiato.

Ho anche notato questo in un'altra risposta [ 1 ].


Si arresta in modo anomalo quando si fa clic sul pulsante Home del dispositivo, con l'opzione "Non conservare attività" nelle opzioni dello sviluppatore.
jul

24
funziona, ma se ruoto lo schermo, la mia app si arresta in modo anomalo con questa eccezione: Causato da: java.lang.IllegalStateException: Impossibile eseguire questa azione dopo onSaveInstanceState
jramoyo

1
Per coloro che potrebbero essere interessati, è possibile eliminare l'eccezione causata dalla rotazione dello schermo memorizzando l'orientamento iniziale nel costruttore del frammento e chiamando la transazione precedente solo se l'orientamento NON è stato modificato. Se è stato modificato, non è necessario lottare con un ID duplicato, poiché non è presente quando la vista viene creata nuovamente dopo la rotazione. Posso modificare la risposta e fornire uno snippet di codice, se a Matt non importa.
Stan,

1
Hmm per me il frammento di mappa non viene rimosso. Devo fare qualcosa di sbagliato ma se chiamo getActivity (). GetSupportFragmentManager (). GetFragments (), il frammento rimane dopo la chiamata remove (f) .commit. Qualcuno ha idea del perché? (Ho sostituito getFragManager con getSupportFragManager)
Daniel Wilson

10
commitAllowingStateLoss eviterà l'eccezione IllegalStateException.
Héctor Júdez Sapena,

22

Questa è la mia risposta:

1, crea un layout xml come il seguente:

<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, nella classe Frammento, aggiungi una mappa di google a livello di codice.

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();ritorna null. Qualche idea sul perché?
Anas Azeem,

@AnasAzeem, la creazione di un frammento risolve il problema a livello di codice, non è necessario ottenere mMap per la soluzione nel tuo caso.
Sì,

@AnasAzeem dovresti usare get getMapAsync che restituirà correttamente un'istanza della mappa dopo l'inizializzazione (in background). Questo è il modo "corretto" di lavorare con google maps e non ha nulla a che fare con i frammenti
Ganesh Krishnan,

10
  1. Come menzionato da @Justin Breitfeller, la soluzione @Vidar Wahlberg è un hack che potrebbe non funzionare nella futura versione di Android.
  2. @Vidar Wahlberg preferisce un hack perché un'altra soluzione potrebbe "ricreare e ridisegnare la mappa, il che non è sempre auspicabile". Il ridisegno della mappa potrebbe essere evitato mantenendo il vecchio frammento della mappa, anziché creare ogni volta una nuova istanza.
  3. La soluzione @Matt non funziona per me (IllegalStateException)
  4. Come citato da @Justin Breitfeller, "Non è possibile gonfiare un layout in un frammento quando quel layout include a. I frammenti nidificati sono supportati solo se aggiunti a un frammento in modo dinamico."

La mia soluzione:

@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
Grazie @Desmond La tua soluzione ha funzionato perfettamente per me. L'unica cosa che devi ricordare è NON creare la mappa nel layout. La creazione di mappe in questa soluzione è incorporata nel codice, quindi cambia <frammento android: name = "com.google.android.gms.maps.SupportMapFragment"> ad es. <LinearLayout id = "@ + id / map />
kosiara - Bartosz Kosarzycki,

7

Dichiarare l'oggetto SupportMapFragment a livello globale

    private SupportMapFragment mapFragment;

Nel metodo onCreateView () inserisci sotto il codice

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

In onDestroyView () inserisci il codice seguente

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

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

Nel tuo file xml inserisci sotto il codice

 <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"
    />

Il codice sopra ha risolto il mio problema e funziona bene


3

Consiglierei replace()piuttosto che attach()/ detach()nella gestione delle tue schede.

Oppure passa a ViewPager. Ecco un progetto di esempio che mostra un ViewPager, con schede, che ospita 10 mappe.


Ma sostituisci dare l'errore outofmemry di quello che dovremmo fare se usiamo la finestra di scollegamento di quando si verifica il problema della mappa.
Sviluppatore Android Vishal,

3

Un'altra soluzione:

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

Questo è tutto, se non nullo non è necessario reinizializzarlo rimuovendolo dal genitore è un passaggio non necessario.


Questa è la soluzione migliore per il mio caso. Stavo ottenendo quell'errore a causa dell'utilizzo di due frammenti per il mio grafico di navigazione e questo ha risolto il mio problema.
Kennedy Kambo,

Questa è la soluzione migliore per il mio caso. Stavo ottenendo quell'errore a causa dell'utilizzo di due frammenti per il mio grafico di navigazione e questo ha risolto il mio problema.
Kennedy Kambo,

2

Oggi ho perso ore per trovare il motivo, per fortuna questo problema non è dovuto all'implementazione di MapFragment, purtroppo non funziona perché i frammenti nidificati sono supportati solo attraverso la libreria di supporto dalla rev 11.

La mia implementazione ha un'attività con barra delle azioni (in modalità a schede) con due schede (senza viewpager), una con la mappa e l'altra con un elenco di voci. Ovviamente sono stato abbastanza ingenuo usare MapFragment all'interno dei miei frammenti di tabulazione, e voilà l'app si è bloccata ogni volta che sono tornato a map-tab.

(Lo stesso problema avrei anche nel caso in cui il mio frammento di tabulazione gonfiasse qualsiasi layout contenente qualsiasi altro frammento).

Un'opzione è usare MapView (invece di MapFragment), con un po 'di overhead (vedi MapView Docs come sostituzione drop-in in layout.xml, un'altra opzione è quella di utilizzare la libreria di supporto dalla versione 11, ma poi adottare un approccio programmatico poiché i frammenti nidificati non sono supportati né tramite layout o semplicemente aggirandosi programmaticamente distruggendo esplicitamente il frammento (come nella risposta di Matt / Vidar), btw: lo stesso effetto si ottiene usando MapView (opzione 1).

Ma in realtà, non volevo perdere la mappa ogni volta che mi separavo, cioè volevo tenerlo in memoria e ripulirlo solo alla chiusura dell'attività, quindi ho deciso semplicemente di nascondere / mostrare la mappa durante la tabulazione, vedere FragmentTransaction / hide


2

Per coloro che stanno ancora riscontrando questo problema, il modo migliore per assicurarsi che non si verifichi questo errore con una mappa in una scheda è estendere il frammento SupportMapFragmentanziché annidare un SupportMapFragmentinterno all'interno del frammento utilizzato per la scheda.

Ho appena funzionato usando a ViewPagercon a FragmentPagerAdapter, con SupportMapFragment nella terza scheda.

Ecco la struttura generale, nota che non è necessario sovrascrivere il onCreateView()metodo e non è necessario gonfiare alcun layout xml:

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

            }
        });

    }


}

Risultato:

inserisci qui la descrizione dell'immagine

Ecco il codice completo della classe con cui ho provato, che include il frammento di segnaposto utilizzato per le prime due schede e il frammento di mappa utilizzato per la terza scheda:

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

Rispetto tutte le risposte ma ho trovato questa soluzione unica: Se n è il numero di schede, allora:

 mViewPager.setOffscreenPageLimit(n);

Esempio: nel caso indicato:

 mViewPager.setOffscreenPageLimit(2);

Visualizza cercapersone implementa una coda, quindi non è necessario lasciarlo rimuovere quel frammento. onCreateView viene chiamato solo una volta.


1

Stai tornando o gonfiando il layout due volte, controlla solo se ti gonfi solo una volta.



0

Hai provato a fare riferimento alla tua MapFragmentclasse personalizzata nel file di layout?

<?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>

potresti postare il codice per il frammento di mappa personalizzato com.nfc.demo.MapFragment
Mina Fawzy

Non sono l'autore del codice - ho appena usato il codice pubblicato in questione. Devi chiedere a Hermann.
JJD

0

Se utilizzerai solo la risposta di Vidar Wahlberg, riceverai un errore quando apri un'altra attività (ad esempio) e torni alla mappa. O nel mio caso apro altre attività e quindi da una nuova attività apri di nuovo la mappa (senza usare il pulsante Indietro). Ma quando combini la soluzione Vidar Wahlberg e la soluzione Matt non avrai eccezioni.

disposizione

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

Frammento

@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

Ho avuto questo in viewPager e il crash è dovuto al fatto che qualsiasi frammento doveva avere un proprio tag, tag duplicati o ID per lo stesso frammento non sono ammessi.


0

Penso che ci fossero alcuni bug nella precedente libreria App-Compat per Child Fragment. Ho provato @Vidar Wahlberg e @ Matt's ans che non funzionavano per me. Dopo aver aggiornato la libreria di appcompat il mio codice funziona perfettamente senza alcuno sforzo aggiuntivo.


0

Le cose da notare qui è che la tua app si bloccherà gravemente in uno dei due casi: -

1) Per riutilizzare nuovamente il frammento con Maps, il frammento di MapView deve essere rimosso quando il frammento che mostra Maps è stato sostituito con un altro frammento nel callback onDestroyView.

altrimenti quando si tenta di gonfiare due volte lo stesso frammento ID duplicato, tag null o ID principale con un altro frammento per l' errore com.google.android.gms.maps.MapFragment .

2) In secondo luogo, non è necessario combinare le operazioni app.Fragment con android.support.v4.app.Fragment API API, ad esempio non utilizzare android.app.FragmentTransaction per rimuovere v4.app.Fragment tipo MapView Fragment. La miscelazione causerà nuovamente un arresto dal lato del frammento.

Ecco lo snippet di codice di esempio per un corretto utilizzo di 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"
       />

Il risultato è simile al seguente: -inserisci qui la descrizione dell'immagine

Spero che possa aiutare qualcuno.


0

In questa soluzione non è necessario prendere una variabile statica;

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

Sono un po 'in ritardo alla festa, ma nessuna di queste risposte mi ha aiutato nel mio caso. Stavo usando mappa di Google come SupportMapFragment e PlaceAutocompleteFragment sia nella mia frammento. Come tutte le risposte hanno indicato il fatto che il problema è che SupportMapFragment è la mappa da ricreare e ridisegnare, ma dopo aver scoperto lo scavo il mio problema era in realtà con PlaceAutocompleteFragment

Quindi ecco la soluzione funzionante per coloro che si trovano ad affrontare questo problema a causa di SupportMapFragment e 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);

    }

E in onDestroyView cancella SupportMapFragment e 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

Prova a impostare un id (android: id = "@ + id / maps_dialog") per il layout principale di mapView. Per me va bene.


0

Chiunque venga qui ora sta riscontrando questo tipo di errore quando si apre uno Dialogo l'altro Fragmentcon Google Places AutocompleteSupportFragment, prova questo one-liner (non so quanto sia sicuro ma funziona per me):

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

prima di eliminare / distruggere il tuo frammento.


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

Perché non inserire una mappa usando l'oggetto MapView invece di MapFragment? Non sono sicuro che ci sia qualche limitazione in MapView, anche se l'ho trovato utile.


Mi dispiace sono venuto a sapere che è la versione precedente deprecata dell'API di Google Maps
Ankur Gautam,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.