MapView внутри фрагмента - указанный дочерний элемент уже имеет родителя

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

  • фрагмент onCreateView() просто возвращает новый FrameLayout
  • фрагмент onActivityCreated() получает MapView из Activity и добавляет его в свою иерархию представлений
  • 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>

Мой 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's 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's 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(), и это все еще дает сбой. Я действительно не понимаю, почему этот подход не работает, любая помощь будет оценена по достоинству.


person Karakuri    schedule 25.02.2012    source источник


Ответы (4)


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

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

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

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

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

person Scott W    schedule 29.03.2012
comment
Прошло некоторое время с тех пор, как я пересматривал этот проект, но фрагменты были для меня довольно новыми в то время, поэтому я подозреваю, что это действительно правильный ответ. - person Karakuri; 06.07.2012
comment
Спасибо за это, не понимал, что происходит, пока вы не показали последний аргумент :) - person lfxgroove; 12.08.2012

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

Корневое представление, которое я вернул в 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;
}
person Karakuri    schedule 25.02.2012
comment
Не могли бы вы опубликовать пример, пожалуйста? - person Pablo Johnson; 12.06.2012

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

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

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

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

mapContainer.addView(mapView);

а у меня все работает. Больше никаких исключений IllegalStateException.

person Kenny C    schedule 25.03.2012

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

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

mapContainer.removeView(mapView);

до тебя

mapContainer.addView(mapView);

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

... как буквально в предыдущей строке.

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

Попробуй это:

if(mapView.getParent() != null) {
    mapContainer.addView(mapView);
}
person straya    schedule 25.02.2012
comment
Я тоже пробовал это, и это не работает. Все равно выдает точно такую ​​же ошибку. - person Karakuri; 25.02.2012
comment
Я также пробовал варианты ((ViewGroup) mapView.getParent()).removeView(mapView), и ничего из этого не работает. - person Karakuri; 25.02.2012
comment
попробуйте выяснить, к какому родителю уже привязан ваш MapView. если это родитель, к которому вы уже намереваетесь добавить его, то он добавляется, чтобы вы могли обработать случай с помощью логики или оператора try/catch. Я использую решение, которое я разместил в комментариях к этой теме: nextlevelandroid.com/?p=114 ( комментарии lindo) это работает и не требует, чтобы каждый friggen FragmentActivity расширял MapActivity! - person straya; 25.02.2012
comment
@straya: не могли бы вы поделиться своим кодом, чтобы его было легко понять. Я просмотрел много сообщений, но не нашел простого решения. - person gypsicoder; 21.05.2012
comment
эй, я попытаюсь собрать аккуратный, общий пример... но сейчас я занят посещением Google IO! XD - person straya; 26.06.2012