Identificación duplicada, etiqueta nula o identificación principal con otro fragmento para com.google.android.gms.maps.MapFragment

334

Tengo una aplicación con tres pestañas.

Cada pestaña tiene su propio archivo de diseño .xml. Main.xml tiene su propio fragmento de mapa. Es el que aparece cuando se inicia la aplicación por primera vez.

Todo funciona bien, excepto cuando cambio entre pestañas. Si intento volver a la pestaña de fragmentos de mapa, aparece este error. Cambiar a y entre otras pestañas funciona bien.

¿Qué podría estar mal aquí?

Esta es mi clase principal y mi main.xml, así como una clase relevante que uso (también encontrará el registro de errores en la parte inferior)

clase principal

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>

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

error

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
Hermann
fuente
intente esto: devuelva super.onCreateView (inflador, contenedor, savedInstanceState); en lugar de super.onCreateView (inflador, contenedor, savedInstanceState); return inflater.inflate (R.layout.main, contenedor, falso);
Nik
no deberías agregar tu fragmento cuando salvadoInstanceState no es nulo.
muyiou
Echa un vistazo a este comentario. Podría ayudarte: stackoverflow.com/questions/15562416/…
Oleksandr
2
Por favor, cambie la respuesta aceptada! ¡Escogiste una respuesta muy mala que crea pérdidas de memoria! La respuesta correcta es esta: stackoverflow.com/a/19815266/902276
Daniele Segato

Respuestas:

400

La respuesta que Matt sugiere funciona, pero hace que el mapa se vuelva a crear y se vuelva a dibujar, lo que no siempre es deseable. Después de muchas pruebas y errores, encontré una solución que me funciona:

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

Como buena medida, aquí está "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>

Espero que esto ayude, pero no puedo garantizar que no tenga ningún efecto adverso.

Editar: Hubo algunos efectos adversos, como al salir de la aplicación y volver a iniciarla. Dado que la aplicación no se cierra por completo necesariamente (sino que se pone en suspensión en segundo plano), el código anterior que envié fallará al reiniciar la aplicación. He actualizado el código a algo que funciona para mí, tanto entrando y saliendo del mapa como saliendo y reiniciando la aplicación, no estoy muy contento con el bit try-catch, pero parece funcionar bastante bien. Al mirar el seguimiento de la pila, se me ocurrió que podría verificar si el fragmento del mapa está en el FragmentManager, sin necesidad del bloque try-catch, código actualizado.

Más ediciones: resulta que necesitas ese try-catch después de todo. Simplemente comprobar que el fragmento del mapa no funcionó tan bien después de todo. Blergh

Vidar Wahlberg
fuente
25
Esta es una respuesta incorrecta -1! Está filtrando una actividad utilizando un modificador estático para una Vista. La causa raíz de este problema es probablemente otra actividad filtrada, que no se puede recolectar basura porque mantiene una fuerte referencia apuntando a ella. En caso de que se produzca una excepción InflateException, ¡está utilizando una vista que tiene un contexto de actividad destruida! Es mejor encontrar otras fugas de memoria en su aplicación, esto resolverá todos los problemas.
tomrozb
1
¿Supongo que podemos usar WeakReference para evitar pérdidas de memoria?
Desmond Lua
2
Esto también funciona para mí +1. No está obligado a utilizar la referencia estática de la vista
Karol Żygłowicz
44
No hagas esto !!! Causa pérdida de memoria. La única razón por la que esto sucede es porque está inflando un fragmento de XML dentro de otro fragmento. ¡No se supone que hagas eso! ¡Debe usar ChildFragmentManager y agregar el fragmento en onViewCreated ()!
Daniele Segato
1
Sí, esto realmente filtrará la actividad. Encontré una solución de trabajo en stackoverflow.com/a/27592123/683763 . La idea es eliminar manualmente el método SupportMapFragmentin onDestroyView.
ULazdins
277

El problema es que lo que intentas hacer no debe hacerse. No deberías estar inflando fragmentos dentro de otros fragmentos. De la documentación de Android :

Nota: No puede inflar un diseño en un fragmento cuando ese diseño incluye un <fragmento>. Los fragmentos anidados solo son compatibles cuando se agregan dinámicamente a un fragmento.

Si bien puede realizar la tarea con los hacks presentados aquí, le sugiero que no lo haga. Es imposible estar seguro de que estos hacks manejarán lo que cada nuevo sistema operativo Android hace cuando intentas inflar un diseño para un fragmento que contiene otro fragmento.

La única forma compatible con Android de agregar un fragmento a otro fragmento es a través de una transacción desde el administrador de fragmentos hijo.

Simplemente cambie su diseño XML en un contenedor vacío (agregue una ID si es necesario):

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

Luego, en el onViewCreated(View view, @Nullable Bundle savedInstanceState)método 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);
}
Justin Breitfeller
fuente
44
Consulte stackoverflow.com/questions/13733299/… para ver ejemplos de cómo crear un fragmento de mapa mediante programación e inicializar el mapa.
Kristopher Johnson
1
Consulte también stackoverflow.com/questions/19239175/… para obtener una solución para un error aparente en la compatibilidad con fragmentos secundarios.
Kristopher Johnson
Encontré este problema 4.3al usar un SupportMapFragmentdefinido en XML. La creación dinámica del fragmento y su inyección en una vista de contenedor resolvió el problema. Ver esta respuesta SO .
theblang
Muy útil respuesta. ¿Cómo podemos hacer una devolución de llamada a la función principal onCreate ()?
Utopía
2
Según el documento, use SupportMapFragment.newInstance(); developers.google.com/maps/documentation/android-api/map
Jemshit Iskenderov
178

Tuve el mismo problema y pude resolverlo eliminando manualmente MapFragmentel onDestroy()método de la Fragmentclase. Aquí hay un código que funciona y hace referencia al MapFragmentby ID en el XML:

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

Si no lo elimina MapFragmentmanualmente, se quedará para que no le cueste muchos recursos recrear / mostrar la vista del mapa nuevamente. Parece que mantener el subyacente MapViewes excelente para alternar entre pestañas, pero cuando se usa en fragmentos, este comportamiento hace MapViewque se cree un duplicado en cada nuevo MapFragmentcon la misma ID. La solución es eliminar manualmente MapFragmenty, por lo tanto, recrear el mapa subyacente cada vez que se infla el fragmento.

También noté esto en otra respuesta [ 1 ].

Mate
fuente
Hace que la aplicación se bloquee al hacer clic en el botón de inicio del dispositivo, con "No mantener actividades" en las opciones del desarrollador.
julio
24
esto funciona, pero si giro mi pantalla, mi aplicación se bloquea con esta excepción: Causado por: java.lang.IllegalStateException: No se puede realizar esta acción después de onSaveInstanceState
jramoyo
1
Para aquellos que puedan estar interesados, puede eliminar la excepción causada por la rotación de la pantalla almacenando la orientación inicial en el constructor del fragmento y llamando a la transacción anterior solo si NO se cambió la orientación. Si se cambió, no hay necesidad de luchar con una ID duplicada, porque no está allí cuando la vista se crea nuevamente después de la rotación. Puedo editar la respuesta y proporcionar un fragmento de código, si a Matt no le importa.
Stan
1
Hmm para mí, el fragmento del mapa no se está eliminando. Debo estar haciendo algo mal, pero si llamo a getActivity (). GetSupportFragmentManager (). GetFragments (), el fragmento permanece después de la llamada remove (f) .commit. ¿Alguien tiene alguna idea de por qué? (Reemplacé getFragManager con getSupportFragManager)
Daniel Wilson
10
commitAllowingStateLoss evitará la excepción IllegalStateException.
Héctor Júdez Sapena
22

Esta es mi respuesta:

1, crea un diseño xml como el siguiente:

<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, en la clase Fragmento, agregue un mapa de Google mediante programación.

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();
    }
} 
Zou
fuente
2
mMapFragment.getMap();vuelve null. ¿Alguna idea de por qué?
Anas Azeem
@AnasAzeem, la creación de fragmentos mediante programación resuelve el problema exactamente, no tiene que obtener mMap para la solución, tal vez en su caso.
yesildal
@AnasAzeem debe usar get getMapAsync que devolverá una instancia de mapa correctamente después de la inicialización (en segundo plano). Esta es la forma "correcta" de trabajar con Google Maps y no tiene nada que ver con fragmentos
Ganesh Krishnan
10
  1. Como mencionó @Justin Breitfeller, la solución @Vidar Wahlberg es un truco que podría no funcionar en futuras versiones de Android.
  2. @Vidar Wahlberg prefiere un hack porque otra solución podría "hacer que el mapa sea recreado y redibujado, lo que no siempre es deseable". Redibujar el mapa podría evitarse manteniendo el antiguo fragmento del mapa, en lugar de crear una nueva instancia cada vez.
  3. La solución @Matt no funciona para mí (IllegalStateException)
  4. Como lo citó @Justin Breitfeller, "No se puede inflar un diseño en un fragmento cuando ese diseño incluye una. Los fragmentos anidados solo son compatibles cuando se agregan dinámicamente a un fragmento".

Mi solución:

@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;
}
Desmond Lua
fuente
2
Gracias @Desmond Su solución funcionó a la perfección para mí. Lo único que debe recordar es NO crear el mapa en el diseño. La creación de mapas en esta solución está incrustada en el código, así que cambie <fragment android: name = "com.google.android.gms.maps.SupportMapFragment"> por ejemplo <LinearLayout id = "@ + id / map />
kosiara - Bartosz Kosarzycki
7

Declarar objeto SupportMapFragment globalmente

    private SupportMapFragment mapFragment;

En el método onCreateView () ponga el código debajo

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

En onDestroyView () poner debajo del código

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

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

En su archivo xml ponga el código debajo

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

El código anterior resolvió mi problema y funciona bien

Digvijay Machale
fuente
3

Recomendaría replace()más que attach()/ detach()en su manejo de pestañas.

O cambia a ViewPager. Aquí hay un proyecto de muestra que muestra ViewPager, con pestañas, alojamiento de 10 mapas.

CommonsWare
fuente
Pero reemplace dar un error de outofmemry de lo que debería hacer si usamos la ventana de separación que ocurre el problema del mapa.
Desarrollador de Android Vishal
3

Otra solución:

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

Eso es todo, si no es nulo, no es necesario reiniciarlo, eliminarlo del padre es un paso innecesario.

AnonymousDev
fuente
Esta es la mejor solución para mi caso. Obtuve ese error como resultado del uso de dos fragmentos para mi gráfico de navegación y esto solucionó mi problema.
Kennedy Kambo
Esta es la mejor solución para mi caso. Obtuve ese error como resultado del uso de dos fragmentos para mi gráfico de navegación y esto solucionó mi problema.
Kennedy Kambo
2

Hoy he perdido horas para encontrar la razón, afortunadamente este problema no se debe a la implementación de MapFragment, desafortunadamente, esto no funciona porque los fragmentos anidados solo son compatibles a través de la biblioteca de soporte de la rev 11.

Mi implementación tiene una actividad con barra de acción (en modo de pestañas) con dos pestañas (sin visor), una con el mapa y la otra con una lista de entradas. Por supuesto, he sido bastante ingenuo al usar MapFragment dentro de mis fragmentos de tabulación, y listo, la aplicación se bloqueaba cada vez que volvía a map-tab.

(El mismo problema que tendría también en caso de que mi fragmento de pestaña infle cualquier diseño que contenga cualquier otro fragmento).

Una opción es usar MapView (en lugar de MapFragment), aunque con algo de sobrecarga (ver MapView Docs como reemplazo directo en el layout.xml, otra opción es usar la biblioteca de soporte desde la versión 11 pero luego tomar un enfoque programático dado que los fragmentos anidados no son compatibles a través del diseño, o simplemente trabajando programáticamente destruyendo explícitamente el fragmento (como en la respuesta de Matt / Vidar), por cierto: se logra el mismo efecto usando MapView (opción 1).

Pero en realidad, no quería perder el mapa cada vez que tabulaba, es decir, quería mantenerlo en la memoria y limpiar solo cuando se cerraba la actividad, así que decidí simplemente ocultar / mostrar el mapa mientras tabulaba, ver FragmentTransaction / hide

comeGetSome
fuente
2

Para aquellos que todavía se encuentran con este problema, la mejor manera de asegurarse de no obtener este error con un Mapa en una pestaña es hacer que el Fragmento se extienda en SupportMapFragmentlugar de anidar SupportMapFragmentdentro del Fragmento utilizado para la Pestaña.

Acabo de hacer que esto funcione usando a ViewPagercon a FragmentPagerAdapter, con SupportMapFragment en la tercera pestaña.

Aquí está la estructura general, tenga en cuenta que no hay necesidad de anular el onCreateView()método, y no hay necesidad de inflar ningún diseño 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)));

            }
        });

    }


}

Resultado:

ingrese la descripción de la imagen aquí

Aquí está el código de clase completo con el que solía probar, que incluye el Fragmento de marcador de posición utilizado para las dos primeras Pestañas, y el Fragmento de Mapa utilizado para la tercera Pestaña:

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

                }
            });

        }
    }
}
Daniel Nugent
fuente
2

Respeto todas las respuestas, pero encontré esta solución: si n es el número de pestañas, entonces:

 mViewPager.setOffscreenPageLimit(n);

Ejemplo: en el caso mencionado:

 mViewPager.setOffscreenPageLimit(2);

View pager implementa una cola para que no tenga que dejar que elimine ese fragmento. onCreateView se llama solo una vez.

Jayant Arora
fuente
1

Está regresando o inflando el diseño dos veces, solo verifique si solo se infla una vez.

Mahdi Giveie
fuente
0

¿Has estado intentando hacer referencia a tu MapFragmentclase personalizada en el archivo de diseño?

<?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>
JJD
fuente
¿podría publicar un código para un fragmento de mapa personalizado com.nfc.demo.MapFragment
Mina Fawzy el
No soy el autor del código, solo utilicé el código publicado en cuestión. Tienes que preguntarle a Hermann.
JJD
0

Si usará solo la respuesta de Vidar Wahlberg, obtendrá un error cuando abra otra actividad (por ejemplo) y vuelva al mapa. O en mi caso, abra otra actividad y luego, desde la nueva actividad, vuelva a abrir el mapa (sin usar el botón Atrás). Pero cuando combine la solución Vidar Wahlberg y la solución Matt no tendrá excepciones.

diseño

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

Fragmento

@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();
}
Vlad Hudnitsky
fuente
0

Tenía esto en viewPager y el bloqueo se debió a que cualquier fragmento tenía que tener su propia etiqueta, no se permiten etiquetas duplicadas o identificadores para el mismo fragmento.

usuario1396018
fuente
0

Creo que hubo algunos errores en la versión anterior de App-Compat para Fragment infantil. Intenté @Vidar Wahlberg y @ Matt's y no funcionaron para mí. Después de actualizar la biblioteca appcompat, mi código se ejecuta perfectamente sin ningún esfuerzo adicional.

maddy d
fuente
0

Lo que debe tener en cuenta aquí es que su aplicación se bloqueará gravemente en cualquiera de los dos casos: -

1) Para poder reutilizar nuevamente el fragmento con Maps, MapView Fragment debe eliminarse cuando el fragmento que muestra Maps se reemplazó con otro fragmento en la devolución de llamada de onDestroyView.

de lo contrario, cuando intente inflar el mismo fragmento dos veces ID duplicado, etiqueta nula o ID principal con otro fragmento para com.google.android.gms.maps.MapFragment se producirá un error de .

2) En segundo lugar, no debe mezclar las operaciones de la aplicación. Fragmento con android.support.v4.app. Mezclar esto nuevamente resultará en un choque desde el lado del fragmento.

Aquí hay un fragmento de código de muestra para el uso correcto 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"
       />

El resultado se ve así: -ingrese la descripción de la imagen aquí

Espero que ayude a alguien.

Hitesh Sahu
fuente
0

En esta solución no necesita tomar una variable estática;

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

}`
Lalit Kumar
fuente
0

Llego un poco tarde a la fiesta, pero ninguna de estas respuestas me ayudó en mi caso. Yo estaba usando Google Map como SupportMapFragment y PlaceAutocompleteFragment tanto en mi fragmento. Como todas las respuestas señalaron el hecho de que el problema es que SupportMapFragment es el mapa que se debe recrear y volver a dibujar, pero después de excavar descubrí que mi problema era en realidad con PlaceAutocompleteFragment

Así que aquí está la solución de trabajo para aquellos que enfrentan este problema debido a SupportMapFragment y 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);

    }

Y en onDestroyView borre SupportMapFragment y 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;
    }


}
Ratul Bin Tazul
fuente
0

Intente configurar un id (android: id = "@ + id / maps_dialog") para su diseño padre mapView. Funciona para mi.

hieudev desarrollo
fuente
0

Cualquier persona que venga aquí ahora que recibe este tipo de error al abrir uno Dialogu otro Fragmentcon Google Places AutocompleteSupportFragment, pruebe esta frase (no sé qué tan seguro es esto, pero funciona para mí):

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

antes de descartar / destruir tu fragmento.

barnacle.m
fuente
-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>

¿Por qué no inserta un mapa usando el objeto MapView en lugar de MapFragment? No estoy seguro de si hay alguna limitación en MapView, aunque lo encontré útil.

Ankur Gautam
fuente
Lo siento, vine a saber que es la versión anterior en desuso de la API de Google Maps
Ankur Gautam