Дублируемый идентификатор, нулевой тег или родительский идентификатор с другим фрагментом для com.google.android.gms.maps.MapFragment

334

У меня есть приложение с тремя вкладками.

Каждая вкладка имеет свой собственный .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
Герман
источник
попробуйте это - вернуть super.onCreateView (inflater, container, saveInstanceState); вместо super.onCreateView (inflater, container, saveInstanceState); вернуть inflater.inflate (R.layout.main, container, false);
Ник
Вы не должны добавлять свой фрагмент, когда saveInstanceState не является нулевым.
Muyiou
Посмотрите на этот комментарий. Это может вам помочь: stackoverflow.com/questions/15562416/…
Александр
2
Пожалуйста, измените принятый ответ! Вы выбрали очень плохой ответ, который создает утечки памяти! Правильный ответ таков
Даниэле Сегато

Ответы:

400

Ответ, который Мэтт предлагает, работает, но заставляет карту пересоздавать и перерисовывать, что не всегда желательно. После многих проб и ошибок я нашел решение, которое работает для меня:

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.

Видар Уолберг
источник
25
Это неправильный ответ -1! Вы пропускаете действие, используя статический модификатор для просмотра. Основной причиной этой проблемы, вероятно, является другая утечка активности, которую нельзя собрать, поскольку вы сохраняете строгое указание на нее. Если выдается исключение InflateException, вы используете представление с контекстом уничтоженной активности! Лучше найти другие утечки памяти в вашем приложении, это решит все проблемы.
Tomrozb
1
Я думаю, мы можем использовать WeakReference, чтобы избежать утечки памяти?
Десмонд Луа
2
Это работает для меня тоже +1. Вы не обязаны использовать статическую ссылку на вид
Karol Żygłowicz
4
Не делай этого !!! Это вызывает утечку памяти. Единственная причина, по которой это происходит, заключается в том, что вы надуваете фрагмент из XML внутри другого фрагмента. Вы НЕ должны это делать! Вы должны использовать ChildFragmentManager и добавить фрагмент в onViewCreated ()!
Даниэле Сегато
1
Да, это действительно утечка активности. Нашел рабочее решение в stackoverflow.com/a/27592123/683763 . Идея состоит в том, чтобы вручную удалить метод SupportMapFragmentin onDestroyView.
ULazdins
277

Проблема в том, что то, что вы пытаетесь сделать, не должно быть сделано. Вы не должны раздувать фрагменты внутри других фрагментов. Из документации Android :

Примечание. Вы не можете раздувать макет на фрагмент, если этот макет содержит <фрагмент>. Вложенные фрагменты поддерживаются только при динамическом добавлении к фрагменту.

Хотя вы можете выполнить задачу с помощью хаков, представленных здесь, я настоятельно рекомендую вам не делать этого. Невозможно быть уверенным, что эти хаки будут обрабатывать то, что делает каждая новая ОС Android, когда вы пытаетесь надуть макет для фрагмента, содержащего другой фрагмент.

Единственный поддерживаемый Android способ добавить фрагмент к другому фрагменту - через транзакцию из дочернего менеджера фрагментов.

Просто измените ваш XML-макет в пустой контейнер (добавьте идентификатор, если необходимо):

<?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)методе Fragment :

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    FragmentManager fm = getChildFragmentManager();
    SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentByTag("mapFragment");
    if (mapFragment == null) {
        mapFragment = new SupportMapFragment();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.mapFragmentContainer, mapFragment, "mapFragment");
        ft.commit();
        fm.executePendingTransactions();
    }
    mapFragment.getMapAsync(callback);
}
Джастин Брайтфеллер
источник
4
См. Stackoverflow.com/questions/13733299/… для примеров создания фрагмента карты программно и инициализации карты.
Кристофер Джонсон
1
Также см. Stackoverflow.com/questions/19239175/… для обхода очевидной ошибки в поддержке дочерних фрагментов.
Кристофер Джонсон
Я столкнулся с этой проблемой 4.3при использовании SupportMapFragmentопределенного в XML. Динамическое создание фрагмента и внедрение его в контейнерное представление решило проблему. Смотрите этот так ответ .
theblang
Очень полезный ответ. Как мы можем сделать обратный вызов основной функции onCreate ()?
Утопия
2
Согласно документу, используйте SupportMapFragment.newInstance(); developers.google.com/maps/documentation/android-api/map
Джемшит Искендеров
178

У меня была такая же проблема и была в состоянии решить ее вручную удалением 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 ].

Matt
источник
Это приводит к сбою приложения при нажатии кнопки «Домой» на устройстве с параметром «Не выполнять действия» в параметрах разработчика.
июля
24
это работает, но если я поворачиваю свой экран, мое приложение аварийно завершает работу с этим исключением: Причина: java.lang.IllegalStateException: Невозможно выполнить это действие после onSaveInstanceState
jramoyo
1
Для тех, кто может быть заинтересован, вы можете устранить исключение, вызванное поворотом экрана, сохраняя начальную ориентацию в конструкторе фрагмента и вызывая вышеуказанную транзакцию, только если ориентация НЕ была изменена. Если оно было изменено, нет необходимости бороться с дублирующим идентификатором, потому что его нет, когда представление создается заново после поворота. Я могу отредактировать ответ и предоставить фрагмент кода, если Мэтт не возражает.
Стан
1
Хм, для меня фрагмент карты не удаляется. Я должен делать что-то не так, но если я вызываю getActivity (). GetSupportFragmentManager (). GetFragments (), фрагмент остается после вызова remove (f) .commit. У кого-нибудь есть идеи почему? (Я заменил getFragManager на getSupportFragManager)
Даниэль Уилсон,
10
commitAllowingStateLoss позволит избежать исключения IllegalStateException.
Гектор Худес Сапена
22

Это мой ответ:

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();
    }
} 
Цзоу
источник
2
mMapFragment.getMap();возвращается null. Есть идеи почему?
Анас Азим
@ AnasAzeem, создание фрагмента программно решает проблему точно, вам не нужно получать mMap для решения, может быть, в вашем случае.
Yesildal
@AnasAzeem, вы должны использовать get getMapAsync, который будет правильно возвращать экземпляр карты после инициализации (в фоновом режиме). Это «правильный» способ работы с картами Google и не имеет ничего общего с фрагментами
Ганеш Кришнан
10
  1. Как упомянул @Justin Breitfeller, решение @Vidar Wahlberg - это хак, который может не сработать в будущей версии Android.
  2. @Vidar Wahlberg предлагает взломать, потому что другое решение может «заставить карту пересоздать и перерисовать, что не всегда желательно». Перерисовку карты можно предотвратить, поддерживая старый фрагмент карты, а не создавая каждый раз новый экземпляр.
  3. Решение @Matt у меня не работает (IllegalStateException)
  4. Как цитирует @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;
}
Десмонд Луа
источник
2
Спасибо @Desmond Ваше решение сработало для меня безупречно. Единственное, что вам нужно запомнить, это НЕ создавать карту в макете. Создание карты в этом решении встроено в код, поэтому измените <фрагмент android: name = "com.google.android.gms.maps.SupportMapFragment">, например, на <LinearLayout id = "@ + id / map />
kosiara - Bartosz Косаржицкий
7

Объявите объект 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"
    />

Выше код решил мою проблему, и она работает нормально

Дигвидж Мачале
источник
3

Я бы рекомендовал replace()вместо attach()/ detach()в вашей вкладке обработки.

Или переключитесь на ViewPager. Вот пример проекта показывая ViewPager, с закладками, хостинг 10 карт.

CommonsWare
источник
Но замените команду «дать ошибку», чем следует делать, если мы используем окно отсоединения, чем возникает проблема с картой.
Android-разработчик Vishal
3

Другое решение:

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

Вот и все, если не NULL, вам не нужно повторно инициализировать его удаление из родителя является ненужным шагом.

AnonymousDev
источник
Это лучшее решение для моего случая. Я получил эту ошибку в результате использования двух фрагментов для моего навигационного графика, и это исправило мою проблему.
Кеннеди Камбо
Это лучшее решение для моего случая. Я получил эту ошибку в результате использования двух фрагментов для моего навигационного графика, и это исправило мою проблему.
Кеннеди Камбо
2

Я потерял часы сегодня, чтобы найти причину, к счастью, эта проблема не из-за реализации MapFragment, к сожалению, это не работает, потому что вложенные фрагменты поддерживаются только через библиотеку поддержки начиная с версии 11.

Моя реализация имеет действие с панелью действий (в режиме вкладок) с двумя вкладками (без окна просмотра), одна из которых имеет карту, а другая - список записей. Конечно, я был довольно наивен, чтобы использовать MapFragment внутри моих фрагментов вкладок, и вуаля приложение вылетало каждый раз, когда я переключался на map-tab.

(Та же проблема, что и у меня, в случае, если мой фрагмент табуляции будет раздувать любой макет, содержащий любой другой фрагмент).

Одним из вариантов является использование MapView (вместо MapFragment), хотя с некоторыми накладными расходами (см. Документы MapView как замену в файле layout.xml, другой вариант - использовать библиотеку поддержки, начиная с версии 11, но затем применять программный подход). так как вложенные фрагменты не поддерживаются ни макетом, ни просто программным путем обхода путем явного уничтожения фрагмента (как в ответе Мэтта / Видара), btw: тот же эффект достигается с помощью MapView (опция 1).

Но на самом деле я не хотел терять карту каждый раз, когда вкладывал, то есть я хотел хранить ее в памяти и очищать только после закрытия активности, поэтому я решил просто скрыть / показать карту во время вкладки, см. FragmentTransaction / hide

comeGetSome
источник
2

Для тех, кто все еще сталкивается с этой проблемой, лучший способ удостовериться, что вы не получите эту ошибку с картой во вкладке, - это расширить фрагмент SupportMapFragmentвместо того, чтобы SupportMapFragmentвкладывать фрагмент, используемый для вкладки.

Я только что получил это, работая ViewPagerс a 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");

                }
            });

        }
    }
}
Даниэль Нугент
источник
2

Я уважаю все ответы, но я нашел это одно линейное решение: если n - количество вкладок, то:

 mViewPager.setOffscreenPageLimit(n);

Пример: В случае, упомянутом:

 mViewPager.setOffscreenPageLimit(2);

View pager реализует очередь, поэтому вам не нужно позволять ему удалять этот фрагмент. onCreateView вызывается только один раз.

Джаянт Арора
источник
1

Вы возвращаете или раздуваете макет дважды, просто проверьте, не раздули ли вы только один раз.

Махди Живи
источник
0

Вы пытались сослаться на свой пользовательский 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>
JJD
источник
Не могли бы вы опубликовать код для пользовательского фрагмента карты com.nfc.demo.MapFragment
Mina Fawzy
Я не автор кода - я просто использовал код, указанный в вопросе. Вы должны спросить Херманна.
JJD
0

Если вы будете использовать только ответ 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();
}
Влад Худницкий
источник
0

У меня это было в viewPager, и сбой был из-за того, что любой фрагмент должен иметь свой собственный тег, дублирующие теги или идентификаторы для одного и того же фрагмента не допускаются.

user1396018
источник
0

Я думаю, что в предыдущей библиотеке App-Compat для дочернего фрагмента были некоторые ошибки. Я попробовал @ Vidar Wahlberg и @ Matt's, и они не работали для меня. После обновления библиотеки appcompat мой код работает без каких-либо дополнительных усилий.

Мэдди д
источник
0

Обратите внимание на то, что ваше приложение будет плохо работать в любом из двух случаев:

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

Результат выглядит так: -введите описание изображения здесь

Надеюсь, это кому-нибудь поможет.

Хите саху
источник
0

В этом решении вам не нужно принимать статические переменные;

Button nextBtn;

private SupportMapFragment mMapFragment;

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

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

    return  mRootView;
}

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

}`
Лалит Кумар
источник
0

Я немного опоздал на вечеринку, но ни один из этих ответов не помог мне в моем случае. Я использовал карту 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;
    }


}
Ратул Бин Тазул
источник
0

Попробуйте установить идентификатор (android: id = "@ + id / maps_dialog") для родительского макета mapView. Работает для меня.

Хеудев Девело
источник
0

Любой, кто приходит сюда сейчас и сталкивается с ошибками такого типа при открытии того Dialogили иного Fragmentс помощью Google Адресов AutocompleteSupportFragment, попробуйте эту однострочную версию (я не знаю, насколько это безопасно, но это работает для меня):

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

прежде чем уволить / уничтожить свой фрагмент.

barnacle.m
источник
-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>

Почему бы вам не вставить карту, используя объект MapView вместо MapFragment? Я не уверен, есть ли какие-либо ограничения в MapView, хотя я нашел это полезным.

Анкур Гаутам
источник
Извините, я узнал, что это устаревшая предыдущая версия API Карт Google
Анкур Гаутам,