Fragment中的MapView - 指定的子节点已经有父节点

时间:2012-02-25 02:23:49

标签: android android-mapview fragment

我正在尝试在片段中显示MapView(使用被黑客入侵的兼容性库)。以下在过去工作得很好:

  • 片段的onCreateView()只会返回一个新的FrameLayout
  • 片段的onActivityCreated()从Acitivity获取MapView并将其添加到其视图层次结构中
  • onDestroyView()从其视图层次结构中删除MapView

现在我希望片段使用xml中定义的布局,以便我可以拥有其他一些UI内容。将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(),但仍然崩溃。我真的不明白为什么这种方法不起作用,任何帮助都会受到赞赏。

4 个答案:

答案 0 :(得分:15)

我不确定这是否与您遇到的问题相同,但事实证明,只要您没有附加到根目录,充气就可以了。

例如,您需要执行以下操作:

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

如果您未能将最后false个参数添加到inflate()调用,那么您将获得IllegalStateException。

我认为正在发生的事情是,如果没有额外的false参数,您的膨胀视图树将附加到根视图(container),然后当Activity启动时,系统会尝试添加视图树再次到根。因此错误。

答案 1 :(得分:1)

在尝试了很多事情之后,我最终做到了这一点:

我在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;
}

答案 2 :(得分:0)

[UPDATE] 我写了一个小库,将所有这些LocalActivityManager解决方案混合在一起:https://github.com/coreform/android-tandemactivities

[老帖子] 你应该

mapContainer.removeView(mapView);

之前

mapContainer.addView(mapView);

避免这种例外。

......就像之前的字面一样。

好的,评论后跟进:

试试这个:

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

答案 3 :(得分:0)

这对我有用... 每当我切换片段时我都会这样做

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

我没有把它放在我的onDestroy()或onPause()方法中。每当我切换片段时我就这样做了。出于某种原因,我的片段没有调用onPause()。

在我的onResume()方法中,我做了

mapContainer.addView(mapView);

一切都适合我。不再有IllegalStateExceptions。