Дублируемый идентификатор, нулевой тег или родительский идентификатор с другим фрагментом для com.google.android.gms.maps.MapFragment
У меня есть приложение с тремя вкладками.
Каждая вкладка имеет свой собственный XML-файл макета. Файл main.xml имеет собственный фрагмент карты. Это тот, который появляется при первом запуске приложения.
Все работает отлично, за исключением случаев, когда я переключаюсь между вкладками. Если я пытаюсь вернуться на вкладку фрагмента карты, я получаю эту ошибку. Переключение на другие вкладки и между ними работает просто отлично.
Что здесь может быть не так?
Это мой основной класс и мой main.xml, а также соответствующий класс, который я использую (вы также найдете журнал ошибок внизу)
основной класс
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>
соответствующий класс ( 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();
}
}
ошибка
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
21 ответ
Ответ, который Мэтт предлагает, работает, но он заставляет карту пересоздавать и перерисовывать, что не всегда желательно. После множества проб и ошибок я нашел решение, которое работает для меня:
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;
}
Для правильной оценки, вот "map.xml" (R.layout.map) с 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>
Я надеюсь, что это помогает, но я не могу гарантировать, что это не будет иметь никаких негативных последствий.
Изменить: Были некоторые негативные последствия, такие как выход из приложения и запуск его снова. Поскольку приложение не обязательно полностью закрывается (а просто переводится в спящий режим в фоновом режиме), предыдущий код, который я отправил, завершится неудачно при перезапуске приложения. Я обновил код, чтобы он работал для меня, входя и выходя из карты, а также выходя и перезапуская приложение, я не слишком доволен битом try-catch, но, похоже, он работает достаточно хорошо. Когда я смотрел на трассировку стека, мне пришло в голову, что я могу просто проверить, находится ли фрагмент карты в FragmentManager, нет необходимости в блоке try-catch, обновлен код.
Больше правок: Оказывается, вам все-таки нужен этот try-catch. В конце концов, проверка фрагмента карты не сработала. Blergh.
Проблема в том, что то, что вы пытаетесь сделать, не должно быть сделано. Вы не должны раздувать фрагменты внутри других фрагментов. Из документации Android:
Примечание. Вы не можете раздувать макет на фрагмент, если этот макет содержит <фрагмент>. Вложенные фрагменты поддерживаются только при динамическом добавлении к фрагменту.
Хотя вы можете выполнить задачу с помощью хаков, представленных здесь, я настоятельно рекомендую вам не делать этого. Невозможно быть уверенным, что эти хаки будут обрабатывать то, что делает каждая новая ОС Android, когда вы пытаетесь надуть макет для фрагмента, содержащего другой фрагмент.
Единственный поддерживаемый Android способ добавить фрагмент к другому фрагменту - через транзакцию из дочернего менеджера фрагментов.
Просто измените ваш XML-макет в пустой контейнер (добавьте ID, если необходимо):
<?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>
Затем во фрагменте onViewCreated(View view, @Nullable Bundle savedInstanceState)
метод:
@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);
}
У меня была та же проблема, и я смог ее решить, удалив вручную MapFragment
в onDestroy()
метод Fragment
учебный класс. Вот код, который работает и ссылается на MapFragment
по идентификатору в XML:
@Override
public void onDestroyView() {
super.onDestroyView();
MapFragment f = (MapFragment) getFragmentManager()
.findFragmentById(R.id.map);
if (f != null)
getFragmentManager().beginTransaction().remove(f).commit();
}
Если вы не удалите MapFragment
вручную, он будет зависать так, чтобы не потребовалось много ресурсов для повторного создания / отображения вида карты. Кажется, что сохранение основного MapView
отлично подходит для переключения между вкладками, но при использовании во фрагментах такое поведение вызывает дублирование MapView
создаваться при каждом новом MapFragment
с тем же идентификатором. Решение состоит в том, чтобы вручную удалить MapFragment
и, таким образом, воссоздайте базовую карту каждый раз, когда фрагмент надувается.
Я также отметил это в другом ответе [ 1].
Это мой ответ:
1, создать макет XML, как показано ниже:
<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, в классе Fragment добавьте карту Google программным способом.
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();
}
}
- Как упомянул @Justin Breitfeller, решение @Vidar Wahlberg - это хак, который может не сработать в будущей версии Android.
- @Vidar Wahlberg предлагает взломать, потому что другое решение может "заставить карту пересоздать и перерисовать, что не всегда желательно". Перерисовку карты можно предотвратить, поддерживая старый фрагмент карты, а не создавая каждый раз новый экземпляр.
- Решение @Matt у меня не работает (IllegalStateException)
- Как цитирует @Justin Breitfeller: "Вы не можете раздувать макет на фрагмент, если этот макет включает в себя. Вложенные фрагменты поддерживаются только при динамическом добавлении к фрагменту".
Мое решение:
@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;
}
Объявите объект SupportMapFragment глобально
private SupportMapFragment mapFragment;
В методе onCreateView() поместите код ниже
mapFragment = (SupportMapFragment) getChildFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
В onDestroyView() поместите код ниже
@Override
public void onDestroyView() {
super.onDestroyView();
if (mapFragment != null)
getFragmentManager().beginTransaction().remove(mapFragment).commit();
}
В вашем XML-файле поместите ниже код
<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"
/>
Выше код решил мою проблему, и она работает нормально
Вы возвращаете или надуваете макет дважды, просто проверьте, надуваете ли вы только один раз.
Другое решение:
if (view == null) {
view = inflater.inflate(R.layout.nearbyplaces, container, false);
}
Вот и все, если не NULL, вам не нужно повторно инициализировать его удаление из родителя является ненужным шагом.
Я бы посоветовал replace()
скорее, чем attach()
/detach()
в вашей вкладке обработки.
Или переключитесь на ViewPager
, Вот пример проекта, показывающего ViewPager
, с вкладками, хостинг 10 карт.
Я немного опоздал на вечеринку, но ни один из этих ответов не помог мне в моем случае. Я использовал карту Google в качестве SupportMapFragment и PlaceAutocompleteFragment в моем фрагменте. Поскольку все ответы указывали на тот факт, что проблема в том, что SupportMapFragment является картой, которая должна быть воссоздана и перерисована. Но после копания выяснилось, что моя проблема была на самом деле с PlaceAutocompleteFragment
Итак, вот рабочее решение для тех, кто сталкивается с этой проблемой из-за SupportMapFragment и 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);
}
И в onDestroyView очистите SupportMapFragment и 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;
}
}
Я уважаю все ответы, но я нашел это одно линейное решение: если n - количество вкладок, то:
mViewPager.setOffscreenPageLimit(n);
Пример: В случае, упомянутом:
mViewPager.setOffscreenPageLimit(2);
View pager реализует очередь, поэтому вам не нужно позволять ему удалять этот фрагмент. onCreateView вызывается только один раз.
Я потерял часы сегодня, чтобы найти причину, к счастью, эта проблема не из-за реализации MapFragment, к сожалению, это не работает, потому что вложенные фрагменты поддерживаются только через библиотеку поддержки начиная с версии 11.
Моя реализация имеет действие с панелью действий (в режиме вкладок) с двумя вкладками (без окна просмотра), одна из которых имеет карту, а другая - список записей. Конечно, я был довольно наивен, чтобы использовать MapFragment внутри моих фрагментов вкладок, и вуаля приложение вылетало каждый раз, когда я переключался на map-tab.
(Та же проблема, что и у меня, в случае, если мой фрагмент табуляции раздувает любой макет, содержащий любой другой фрагмент).
Одним из вариантов является использование MapView (вместо MapFragment), хотя с некоторыми накладными расходами (см. Документы MapView в качестве замены в файле layout.xml, другой вариант - использовать библиотеку поддержки, начиная с версии 11, но затем применять программный подход). так как вложенные фрагменты не поддерживаются ни макетом, ни просто программным путем обхода путем явного уничтожения фрагмента (как в ответе Мэтта / Видара), btw: тот же эффект достигается с помощью MapView (опция 1).
Но на самом деле я не хотел терять карту каждый раз, когда вкладывал, то есть я хотел сохранить ее в памяти и очищать только после закрытия активности, поэтому я решил просто скрыть / показать карту во время вкладки, см. FragmentTransaction / hide
Для тех, кто все еще сталкивается с этой проблемой, лучший способ удостовериться, что вы не получите эту ошибку с картой во вкладке, это сделать фрагмент расширенным SupportMapFragment
вместо того, чтобы вложить SupportMapFragment
внутри фрагмента, используемого для табуляции.
Я только что получил это с помощью ViewPager
с FragmentPagerAdapter
, с SupportMapFragment в третьей вкладке.
Вот общая структура, обратите внимание, что нет необходимости переопределять onCreateView()
метод, и нет необходимости раздувать любой макет 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)));
}
});
}
}
Результат:
Вот полный код класса, который я использовал для тестирования, включая фрагмент-заполнитель, использованный для первых двух вкладок, и фрагмент карты, использованный для третьей вкладки:
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");
}
});
}
}
}
Я думаю, что в предыдущей библиотеке App-Compat для дочернего фрагмента были некоторые ошибки. Я попробовал @Vidar Wahlberg и @Matt's, и они не работали для меня. После обновления библиотеки appcompat мой код работает без каких-либо дополнительных усилий.
У меня это было в viewPager, и сбой был из-за того, что любой фрагмент должен иметь свой собственный тег, дублирующие теги или идентификаторы для одного и того же фрагмента не допускаются.
Обратите внимание на то, что ваше приложение будет плохо работать в любом из двух случаев:
1) Для повторного использования фрагмента с Картами фрагмент MapView должен быть удален, когда ваш фрагмент с Картами был заменен другим фрагментом в обратном вызове onDestroyView.
иначе, если вы попытаетесь раздувать один и тот же фрагмент дважды , возникает ошибка Дублировать идентификатор, нулевой тег или идентификатор родителя с другим фрагментом для com.google.android.gms.maps.MapFragment.
2) Во-вторых, вы не должны смешивать операции app.Fragment с операциями API android.support.v4.app.Fragment, например, не использовать android.app.FragmentTransaction для удаления v4.app.Fragment типа MapView Fragment. Смешивание этого снова приведет к падению со стороны фрагмента.
Вот пример кода для правильного использования 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"
/>
Надеюсь, это кому-нибудь поможет.
В этом решении вам не нужно принимать статические переменные;
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());
}
});
}`
Вложенные фрагменты в настоящее время не поддерживаются. Попробуйте пакет поддержки, редакция 11.
Любой, кто приходит сюда сейчас, получает такую ошибку при открытии Dialog
или другой Fragment
с Google Адреса AutocompleteSupportFragment
, попробуйте этот однострочный (я не знаю, насколько это безопасно, но у меня это работает):
autocompleteFragment.getFragmentManager().beginTransaction().remove(autocompleteFragment).commit();
прежде, чем вы отклоните / уничтожите свой фрагмент.
Попробуйте установить идентификатор (android:id="@+id/maps_dialog") для родительского макета mapView. Работает для меня.
Вы пытались ссылаться на свой обычай MapFragment
класс в файле макета?
<?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>
Если вы будете использовать только ответ Vidar Wahlberg, вы получите ошибку, когда откроете другую деятельность (например) и вернетесь к карте. Или, в моем случае, откройте другую активность, а затем из новой активности снова откройте карту (без использования кнопки назад). Но когда вы объедините решение Vidar Wahlberg и решение Matt, у вас не будет исключений.
расположение
<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>
Фрагмент
@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();
}
<?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>
Почему бы вам не вставить карту, используя объект MapView вместо MapFragment? Я не уверен, есть ли какие-либо ограничения в MapView, хотя я нашел это полезным.