ID duplicado, marca nula ou ID pai com outro fragmento para com.google.android.gms.maps.MapFragment

334

Eu tenho um aplicativo com três guias.

Cada guia possui seu próprio arquivo .xml de layout. O main.xml possui seu próprio fragmento de mapa. É o que aparece quando o aplicativo é iniciado pela primeira vez.

Tudo funciona bem, exceto quando eu alterno entre as guias. Se eu tentar voltar à guia de fragmentos do mapa, recebo este erro. Mudar para e entre outras guias funciona perfeitamente.

Oque pode estar errado aqui?

Esta é minha classe principal e meu main.xml, bem como uma classe relevante que eu uso (você também encontrará o log de erros na parte inferior)

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

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

erro

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
fonte
tente isso - retorne super.onCreateView (inflater, container, savedInstanceState); em vez de super.onCreateView (inflater, container, savedInstanceState); retornar inflater.inflate (R.layout.main, container, false);
Nik
você não deve adicionar seu fragmento quando savedInstanceState não for nulo.
muyiou
Dê uma olhada neste comentário. Pode ajudá-lo: stackoverflow.com/questions/15562416/…
Oleksandr
2
Por favor, altere a resposta aceita! Você escolheu uma resposta muito ruim que cria vazamentos de memória! A resposta correta é esta: stackoverflow.com/a/19815266/902276
Daniele Segato

Respostas:

400

A resposta que Matt sugere funciona, mas faz com que o mapa seja recriado e redesenhado, o que nem sempre é desejável. Após várias tentativas e erros, encontrei uma solução que funciona para mim:

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

Para uma boa medida, aqui está "map.xml" (R.layout.map) com 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 isso ajude, mas não posso garantir que não tenha efeitos adversos.

Editar: houve alguns efeitos adversos, como ao sair do aplicativo e iniciá-lo novamente. Como o aplicativo não é necessariamente completamente desligado (mas apenas adormecido em segundo plano), o código anterior que eu enviei falharia ao reiniciar o aplicativo. Atualizei o código para algo que funcione para mim, entrando e saindo do mapa e saindo e reiniciando o aplicativo. Não estou muito feliz com o bit try-catch, mas parece funcionar bem o suficiente. Ao olhar para o rastreamento da pilha, ocorreu-me que eu poderia apenas verificar se o fragmento do mapa está no FragmentManager, sem necessidade do bloco try-catch, o código atualizado.

Mais edições: afinal, você precisa dessa tentativa de captura. A verificação do fragmento do mapa acabou por não funcionar tão bem, afinal. Blergh.

Vidar Wahlberg
fonte
25
Esta é uma resposta errada -1! Você está vazando uma atividade usando o modificador estático para uma Visualização. A causa raiz desse problema é provavelmente outra atividade que vazou, que não pode ser coletada como lixo porque você mantém uma forte referência apontando para ele. Caso haja uma InflateException lançada, você está usando uma exibição que tenha contexto de atividade destruída! Melhor encontrar outros vazamentos de memória no seu aplicativo, isso resolverá todos os problemas.
tomrozb
1
Acho que podemos usar o WeakReference para evitar vazamento de memória?
Desmond Lua
2
Isso funciona para mim também com +1. Você não é obrigado a usar referência de vista estático
Karol Żygłowicz
4
Não faça isso !!! Isso causa vazamento de memória. A única razão pela qual isso acontece é porque você está inflando um fragmento do XML dentro de outro fragmento. Você NÃO deveria fazer isso! Você deve usar o ChildFragmentManager e adicionar o fragmento em onViewCreated ()!
Daniele Segato
1
Sim, isso realmente vazará a atividade. Encontrei uma solução funcional em stackoverflow.com/a/27592123/683763 . A idéia é remover manualmente o método SupportMapFragmentin onDestroyView.
ULazdins
277

O problema é que o que você está tentando fazer não deve ser feito. Você não deve inflar fragmentos dentro de outros fragmentos. Da documentação do Android :

Nota: Você não pode inflar um layout em um fragmento quando esse layout inclui um <fragment>. Fragmentos aninhados são suportados apenas quando adicionados a um fragmento dinamicamente.

Embora você possa realizar a tarefa com os hacks apresentados aqui, sugiro que você não faça isso. É impossível ter certeza de que esses hacks manipularão o que cada novo sistema operacional Android faz quando você tenta aumentar o layout de um fragmento contendo outro fragmento.

A única maneira suportada pelo Android de adicionar um fragmento a outro fragmento é através de uma transação do gerenciador de fragmentos filho.

Simplesmente altere seu layout XML em um contêiner vazio (adicione um ID, se necessário):

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

Então, no 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
fonte
4
Consulte stackoverflow.com/questions/13733299/… para obter exemplos de criação de um fragmento de mapa programaticamente e inicialização do mapa.
2141313 Kristopher Johnson
1
Consulte também stackoverflow.com/questions/19239175/… para obter uma solução alternativa para erros aparentes no suporte a fragmentos filho.
Kristopher Johnson
Encontrei esse problema 4.3ao usar um SupportMapFragmentdefinido em XML. Criar dinamicamente o fragmento e injetá-lo em uma exibição de contêiner resolveu o problema. Veja esta resposta SO .
theblang
Resposta muito útil. Como podemos fazer um retorno de chamada para a função principal onCreate ()?
Utopia
2
De acordo com o documento, use SupportMapFragment.newInstance(); developers.google.com/maps/documentation/android-api/map
Jemshit Iskenderov
178

Eu tive o mesmo problema e consegui resolvê-lo removendo manualmente MapFragmento onDestroy()método da Fragmentclasse Aqui está o código que funciona e faz referência ao MapFragmentpor ID no XML:

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

Se você não remover o MapFragmentmanual, ele ficará suspenso para não custar muitos recursos para recriar / mostrar a visualização do mapa novamente. Parece que manter o subjacente MapViewé ótimo para alternar entre as guias, mas quando usado em fragmentos, esse comportamento faz com que uma duplicata MapViewseja criada sobre cada novo MapFragmentcom o mesmo ID. A solução é remover manualmente oMapFragment e recriar o mapa subjacente sempre que o fragmento for inflado.

Também observei isso em outra resposta [ 1 ].

Matt
fonte
Isso faz com que o aplicativo falhe ao clicar no botão inicial do dispositivo, com as opções "Não manter atividades" nas opções do desenvolvedor.
jul
24
Isso funciona, mas se eu girar minha tela, meu aplicativo falhar com uma exceção: Causada por: java.lang.IllegalStateException: Não é possível executar essa ação após onSaveInstanceState
jramoyo
1
Para aqueles que possam estar interessados, é possível eliminar a exceção causada pela rotação da tela armazenando a orientação inicial no construtor do fragmento e chamando a transação acima apenas se a orientação NÃO foi alterada. Se foi alterado, não há necessidade de lutar com o ID duplicado, porque não existe quando a exibição é criada novamente após a rotação. Posso editar a resposta e fornecer um trecho de código, se Matt não se importar.
Stan
1
Hmm, para mim, o fragmento do mapa não está sendo removido. Devo estar fazendo algo errado, mas se eu chamar getActivity (). GetSupportFragmentManager (). GetFragments (), o fragmento permanecerá após a chamada remove (f) .commit. Alguém tem alguma idéia do porquê? (I substituído getFragManager com getSupportFragManager)
Daniel Wilson
10
commitAllowingStateLoss evitará a exceção IllegalStateException.
Héctor Júdez Sapena
22

Esta é a minha resposta:

1, crie um xml de layout como a seguir:

<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, na classe Fragment, adicione um mapa do Google programaticamente.

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
fonte
2
mMapFragment.getMap();retorna null. Alguma idéia do porquê?
Anas Azeem
@ AnasAzeem, a criação de fragmentos programaticamente resolve o problema exatamente, você não precisa obter o mMap para a solução, talvez no seu caso.
yesildal
@AnasAzeem, você deve usar get getMapAsync, que retornará uma instância de mapa corretamente após a inicialização (em segundo plano). Esta é a maneira "correta" de trabalhar com os mapas do Google e não tem nada a ver com fragmentos
Ganesh Krishnan
10
  1. Conforme mencionado por Justin Breitfeller, a solução @Vidar Wahlberg é um hack que pode não funcionar na versão futura do Android.
  2. O @Vidar Wahlberg adia um hack porque outra solução pode "fazer com que o mapa seja recriado e redesenhado, o que nem sempre é desejável". O redesenho do mapa pode ser evitado mantendo o fragmento antigo do mapa, em vez de criar uma nova instância sempre.
  3. A solução @Matt não funciona para mim (IllegalStateException)
  4. Conforme citado por Justin Breitfeller, "Você não pode inflar um layout em um fragmento quando esse layout inclui a. Fragmentos aninhados são suportados apenas quando adicionados dinamicamente a um fragmento".

Minha solução:

@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
fonte
2
Obrigado @Desmond Sua solução funcionou perfeitamente para mim. A única coisa que você precisa lembrar é NÃO criar o mapa no layout. A criação de mapas nesta solução é incorporada no código, então altere <fragment android: name = "com.google.android.gms.maps.SupportMapFragment"> para, por exemplo, <LinearLayout id = "@ + id / map />
kosiara - Bartosz Kosarzycki 11/11
7

Declarar o objeto SupportMapFragment globalmente

    private SupportMapFragment mapFragment;

No método onCreateView (), coloque o código abaixo

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

Em onDestroyView (), coloque o código abaixo

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

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

No seu arquivo xml, coloque o código abaixo

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

O código acima resolveu meu problema e está funcionando bem

Digvijay Machale
fonte
3

Eu recomendaria, em replace()vez de attach()/ detach()no seu tratamento de guias.

Ou mude para ViewPager. Aqui está um exemplo de projeto mostrando um ViewPager, com guias, hospedando 10 mapas.

CommonsWare
fonte
Porém, substitua o erro outofmemry do que o que deve fazer se usarmos a janela de desanexação do que ocorrer um problema no mapa.
Desenvolvedor Android Vishal
3

Outra solução:

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

É isso, se não for nulo, você não precisará reinicializá-lo. A remoção do pai é uma etapa desnecessária.

AnonymousDev
fonte
Esta é a melhor solução para o meu caso. Eu estava recebendo esse erro como resultado do uso de dois fragmentos no gráfico de navegação e isso corrigiu o problema.
Kennedy Kambo
Esta é a melhor solução para o meu caso. Eu estava recebendo esse erro como resultado do uso de dois fragmentos no gráfico de navegação e isso corrigiu o problema.
Kennedy Kambo
2

Hoje perdi horas para encontrar o motivo. Felizmente, esse problema não é causado pela implementação do MapFragment. Infelizmente, isso não funciona porque os fragmentos aninhados são suportados apenas pela biblioteca de suporte da rev 11.

Minha implementação possui uma atividade com a barra de ação (no modo com guias) com duas guias (sem visor), uma com o mapa e a outra com uma lista de entradas. É claro que tenho sido ingênuo em usar o MapFragment dentro dos meus fragmentos de tabulação, e pronto, o aplicativo falhava toda vez que eu voltava para a tab-map.

(O mesmo problema que eu também teria caso meu fragmento de tabulação inflasse qualquer layout que contenha qualquer outro fragmento).

Uma opção é usar o MapView (em vez de MapFragment), mas com alguma sobrecarga (consulte o MapView Docs como substituto do layout.xml, outra opção é usar a biblioteca de suporte da versão 11, mas adotar a abordagem programática como os fragmentos aninhados não são suportados pelo layout ou apenas trabalhando de forma programática destruindo explicitamente o fragmento (como na resposta de Matt / Vidar), btw: o mesmo efeito é alcançado usando o MapView (opção 1).

Mas, na verdade, eu não queria perder o mapa toda vez que separava, ou seja, queria mantê-lo na memória e limpar apenas após o fechamento da atividade, por isso decidi simplesmente ocultar / mostrar o mapa enquanto guia, consulte FragmentTransaction / hide

comeGetSome
fonte
2

Para aqueles que ainda estão enfrentando esse problema, a melhor maneira de garantir que você não receba esse erro com um mapa em uma guia é fazer com que o fragmento se estenda em SupportMapFragmentvez de aninhar SupportMapFragmentdentro do fragmento usado para a guia.

Acabei de trabalhar com um ViewPagercom um FragmentPagerAdapter, com o SupportMapFragment na terceira guia.

Aqui está a estrutura geral, observe que não há necessidade de substituir o onCreateView()método e não há necessidade de inflar nenhum xml de layout:

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:

insira a descrição da imagem aqui

Aqui está o código de classe completo que eu costumava testar, que inclui o fragmento de espaço reservado usado nas duas primeiras guias e o fragmento de mapa usado na terceira guia:

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

Eu respeito todas as respostas, mas eu encontrei esta solução de uma linha: Se n é o número de guias, então:

 mViewPager.setOffscreenPageLimit(n);

Exemplo: No caso mencionado:

 mViewPager.setOffscreenPageLimit(2);

Visualizar pager implementa uma fila para que você não precise remover esse fragmento. onCreateView é chamado apenas uma vez.

Jayant Arora
fonte
1

Você está retornando ou inflando o layout duas vezes, basta verificar se você inflou apenas uma vez.

Mahdi Giveie
fonte
0

Você está tentando fazer referência à sua MapFragmentclasse personalizada no arquivo de 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>
JJD
fonte
você poderia agradar código postal para com.nfc.demo.MapFragment fragmento de mapa personalizado
Mina Fawzy
Eu não sou o autor do código - apenas usei o código postado em questão. Você tem que perguntar a Hermann.
JJD
0

Se você usar apenas a resposta Vidar Wahlberg, receberá um erro ao abrir outra atividade (por exemplo) e voltar ao mapa. Ou, no meu caso, abra outra atividade e, a partir da nova atividade, abra o mapa novamente (sem usar o botão voltar). Mas quando você combina a solução Vidar Wahlberg e a solução Matt, não há exceções.

disposiçã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
fonte
0

Eu tinha isso no viewPager e a falha ocorreu porque qualquer fragmento tinha que ter sua própria tag, não são permitidas tags duplicadas ou ids para o mesmo fragmento.

user1396018
fonte
0

Eu acho que houve alguns erros na versão anterior da App-Compat para fragmento filho. Tentei @Vidar Wahlberg e @ Matt e eles não funcionaram para mim. Depois de atualizar a biblioteca appcompat, meu código funciona perfeitamente sem nenhum esforço extra.

maddy d
fonte
0

O que deve ser observado aqui é que seu aplicativo falhará muito em um dos dois casos: -

1) Para reutilizar o fragmento com o Maps novamente, o Fragmento MapView deve ser removido quando o fragmento que mostra o Maps foi substituído por outro fragmento no retorno de chamada onDestroyView.

caso contrário, quando você tentar inflar o mesmo fragmento duas vezes , ocorrerá um erro de ID duplicado, tag nulo ou pai com outro fragmento para o erro com.google.android.gms.maps.MapFragment .

2) Em segundo lugar, você não deve misturar as operações app.Fragment com as operações da API android.support.v4.app.Fragment, por exemplo, não use android.app.FragmentTransaction para remover o tipo v4.app.Fragment MapView Fragment. Misturar isso resultará novamente em uma falha do lado do fragmento.

Aqui está um exemplo de trecho de código para o uso correto do 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"
       />

O resultado fica assim: -insira a descrição da imagem aqui

Espero que ajude alguém.

Hitesh Sahu
fonte
0

Nesta solução, você não precisa pegar variáveis ​​estáticas;

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

Estou um pouco atrasado para a festa, mas nenhuma dessas respostas me ajudou no meu caso. Eu estava usando o mapa do Google como SupportMapFragment e PlaceAutocompleteFragment, ambos no meu fragmento. Como todas as respostas apontaram para o fato de que o problema é o SupportMapFragment ser o mapa a ser recriado e redesenhado.

Então, aqui está a solução de trabalho para quem está enfrentando esse problema por causa do 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 no onDestroyView desmarque as opções 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;
    }


}
Ratul Bin Tazul
fonte
0

Tente definir um ID (android: id = "@ + id / maps_dialog") para o layout pai do mapView. Funciona para mim.

hieudev develo
fonte
0

Qualquer pessoa que esteja aqui agora que está recebendo esse tipo de erro ao abrir um Dialogou outro Fragmentcom o Google Places AutocompleteSupportFragment, tente esta lista (não sei o quão seguro isso é, mas funciona para mim):

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

antes de descartar / destruir seu fragmento.

barnacle.m
fonte
-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 que você não insere um mapa usando o objeto MapView em vez de MapFragment? Não tenho certeza se há alguma limitação no MapView, embora eu tenha achado útil.

Ankur Gautam
fonte
Desculpe eu vim a saber que é a versão anterior obsoleto do Google Maps API
Ankur Gautam