MapView внутри Fragment - указанный дочерний элемент уже имеет родителя - PullRequest
10 голосов
/ 25 февраля 2012

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

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

Ответы [ 4 ]

15 голосов
/ 29 марта 2012

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

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

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

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

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

1 голос
/ 26 февраля 2012

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

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

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

EDIT

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

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;
}
0 голосов
/ 25 марта 2012

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

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

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

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

mapContainer.addView(mapView);

, и у меня все работает.Нет больше IllegalStateExceptions.

0 голосов
/ 25 февраля 2012

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

[старая запись] Вам следует

mapContainer.removeView(mapView);

перед вами

mapContainer.addView(mapView);

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

... как в буквальном смысле строки до.

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

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

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