MapView внутри Fragment - указанный ребенок уже имеет родителя

Я пытаюсь показать MapView внутри фрагмента (используя взломанную библиотеку совместимости). В прошлом прошло очень хорошо:

  • onCreateView() просто возвращает новый FrameLayout
  • фрагмент onActivityCreated() получает MapView из Acitivity и добавляет его в свою иерархию представлений
  • onDestroyView() удаляет MapView из его иерархии представлений

Теперь я хотел бы, чтобы фрагмент использовал макет, определенный в xml, чтобы я мог использовать некоторые другие элементы интерфейса. Помещение элемента MapView в файл макета всегда сбой, поэтому я делаю это следующим образом:

map_screen_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >


    <FrameLayout
        android:id="@+id/map_container"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    </FrameLayout>

</LinearLayout>

My MapScreenActivity содержит фактический MapView, а фрагмент вызывает getMapView(), поэтому я не сталкиваюсь с проблемой "не может иметь более одного MapView":

MapScreenActivity.java

public class MapScreenActivity extends FragmentActivity {
    protected Fragment fragment;
    protected MapView mapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.single_pane_empty);

        if (savedInstanceState == null) {
            fragment = new MapScreenFragment();

            getSupportFragmentManager().beginTransaction().add(R.id.root_container, fragment)
                    .commit();
        }
    }

    public MapView getMapView() {
        if (mapView == null) {
            mapView = new MapView(this, getResources().getString(R.string.maps_api_key));
        }

        return mapView;
    }
}

MapScreenFragment.java

public class MapScreenFragment extends Fragment {
    protected ViewGroup mapContainer;
    protected MapView mapView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle args) {
        View root = inflater.inflate(R.layout.map_screen_fragment, container);
        mapContainer = (ViewGroup) root.findViewById(R.id.map_container);
        return root;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        mapView = ((MapScreenActivity) getActivity()).getMapView();
        mapView.setClickable(true);
        mapView.setBuiltInZoomControls(true);

        mapContainer.addView(mapView);

    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mapContainer.removeView(mapView);
    }   

}

Теоретически это должно работать так же, как описанный выше метод new FrameLayout. Тем не менее, я получаю это каждый раз:

02-24 18:01:28.139: E/AndroidRuntime(502): FATAL EXCEPTION: main
02-24 18:01:28.139: E/AndroidRuntime(502): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mapfragment/com.example.mapfragment.MapScreenActivity}: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child parent first.
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1647)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.access$1500(ActivityThread.java:117)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.os.Handler.dispatchMessage(Handler.java:99)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.os.Looper.loop(Looper.java:130)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.main(ActivityThread.java:3683)
02-24 18:01:28.139: E/AndroidRuntime(502):  at java.lang.reflect.Method.invokeNative(Native Method)
02-24 18:01:28.139: E/AndroidRuntime(502):  at java.lang.reflect.Method.invoke(Method.java:507)
02-24 18:01:28.139: E/AndroidRuntime(502):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
02-24 18:01:28.139: E/AndroidRuntime(502):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
02-24 18:01:28.139: E/AndroidRuntime(502):  at dalvik.system.NativeStart.main(Native Method)
02-24 18:01:28.139: E/AndroidRuntime(502): Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child parent first.
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.view.ViewGroup.addViewInner(ViewGroup.java:1976)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.view.ViewGroup.addView(ViewGroup.java:1871)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.view.ViewGroup.addView(ViewGroup.java:1828)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.view.ViewGroup.addView(ViewGroup.java:1808)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.NoSaveStateFrameLayout.wrap(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.FragmentManagerImpl.moveToState(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.FragmentManagerImpl.moveToState(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.BackStackRecord.run(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.FragmentManagerImpl.execPendingActions(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.FragmentActivity.onStart(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1129)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.Activity.performStart(Activity.java:3791)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1620)
02-24 18:01:28.139: E/AndroidRuntime(502):  ... 11 more

Я попытался удалить MapView из него родительский, прежде чем вернуться с getMapView(), и это все еще сбой. Я действительно не понимаю, почему этот подход не работает, любая помощь вообще будет оценена.

Ответ 1

Я не уверен, что это та же проблема, что и у вас, но оказалось, что раздувание в порядке, если вы не присоединяетесь к корню.

Например, вам нужно сделать что-то вроде этого:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.id.my_layout, container, false);
}

Если вы не смогли добавить этот последний аргумент false к вызову inflate(), тогда вы получите исключение IllegalStateException.

Я думаю, что происходит то, что без дополнительного аргумента false ваше раздутое дерево представлений прикрепляется к корневому представлению (container), а затем, когда действие запускается, система пытается добавить дерево представления к корень снова. Отсюда ошибка.

Ответ 2

Попробовав множество вещей, я закончил это:

Корневой вид, который я вернул в onCreateView(), создается программно, а не завышен. Однако раздувание других представлений и добавление их в качестве дочерних элементов в программно созданный корневой вид не вызывает никаких проблем.

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

ИЗМЕНИТЬ

Прошло некоторое время с тех пор, как я пересматривал эту тему, но, насколько я помню, этот не будет работать для меня...

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
    return inflater.inflate(R.layout.some_layout, container, false);
}

..., тогда как будет работать для меня...

private ViewGroup mapContainer;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
    mapContainer = new LinearLayout(getActivity());
    return mapContainer;
}

... и позже в onActivityCreated() я получил бы MapView из действия и добавлю его как дочерний элемент mapContainer. Если бы мне нужны другие представления (например, заголовок выше MapView), я мог бы раздувать их отдельно и добавлять их в mapContainer, как показывает этот фрагмент кода.

private ViewGroup mapContainer;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
    mapContainer = new LinearLayout(getActivity());
    mapContainer.setOrientation(LinearLayout.VERTICAL);
    View headerView = inflater.inflate(R.layout.some_layout, mapContainer, false);
    mapContainer.addView(headerView);
    return mapContainer;
}

Ответ 3

[ОБНОВЛЕНИЕ] Я написал небольшую библиотеку, объединив все эти решения LocalActivityManager: https://github.com/coreform/android-tandemactivities

[старое сообщение] Вы должны

mapContainer.removeView(mapView);

перед вами

mapContainer.addView(mapView);

чтобы избежать такого исключения.

... как буквально строка раньше.

ОК, после комментариев:

Попробуйте следующее:

if(mapView.getParent() != null) {
    mapContainer.addView(mapView);
}

Ответ 4

Это то, что сработало для меня... Всякий раз, когда я переключал фрагменты, я делал

if (mapContainer.getChildAt(0) != null){
        mapContainer.removeViewAt(0);
}

Я не применял это в своих методах onDestroy() или onPause(). Я делал это, когда я переключал фрагменты. По какой-то причине мой фрагмент не вызывал onPause().

В моем методе onResume() я сделал

mapContainer.addView(mapView);

и все работает для меня. Не более IllegalStateExceptions.