在一个Fragment中使用MapView(蜂巢版)

81

现在最新的Google API已经发布,那么创建一个带有MapView的Fragment有什么最佳方法? MapView需要MapActivity才能正常工作。

让Activity来管理继承自MapActivity的Fragment(这是个不好的解决方案,因为它违反了Fragments自我封装的理念),并使用普通的基于xml的布局并不能起作用。我在MapActivity.setupMapView()中得到了一个NullPointerException:

E/AndroidRuntime(  597): Caused by: java.lang.NullPointerException
E/AndroidRuntime(  597):    at com.google.android.maps.MapActivity.setupMapView(MapActivity.java:400)
E/AndroidRuntime(  597):    at com.google.android.maps.MapView.(MapView.java:289)
E/AndroidRuntime(  597):    at com.google.android.maps.MapView.(MapView.java:264)
E/AndroidRuntime(  597):    at com.google.android.maps.MapView.(MapView.java:247)

我的第二个想法是以编程方式创建MapView,并将相关联的Activity(通过getActivity())作为Context传递给MapView构造函数。但这并不起作用:

E/AndroidRuntime(834): Caused by: java.lang.IllegalArgumentException: MapViews can only be created inside instances of MapActivity.
E/AndroidRuntime(834):     at com.google.android.maps.MapView.(MapView.java:291)
E/AndroidRuntime(834):     at com.google.android.maps.MapView.(MapView.java:235)
E/AndroidRuntime(834):     at de.foo.FinderMapFragment.onCreateView(FinderMapFragment.java:225)
E/AndroidRuntime(834):     at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:708)
E/AndroidRuntime(834):     at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:900)
E/AndroidRuntime(834):     at android.app.FragmentManagerImpl.addFragment(FragmentManager.java:978)
E/AndroidRuntime(834):     at android.app.Activity.onCreateView(Activity.java:4090)
E/AndroidRuntime(834):     at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:664)

实际上应该有类似于MapFragment的东西可以处理MapView需要的后台线程,我猜...那么现在最好的做法是什么呢?

来自德国的问候, 瓦伦丁


2
我针对这个问题报告了一个功能请求。请给它点赞。http://code.google.com/p/android/issues/detail?id=15347 - Jeremy Edwards
12个回答

87

4
如果您希望MapActivity能够与父Activity通信(使用Fragment很容易实现),则可以使用Activity.getParent()方法。 - Dori
3
不一定需要使用TabHost,你也可以使用LocalActivityManager,并将返回的窗口附加到片段的内容中。 - ChristophK
4
如果有其他人想知道,这是代码:Intent i = new Intent(getActivity().getParent(), MyMapActivity.class); Window w = localActivityManager.startActivity("tag", i); currentView=w.getDecorView(); currentView.setVisibility(View.VISIBLE); currentView.setFocusableInTouchMode(true); ((ViewGroup) currentView).setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); this.contentView.addView(currentView); - ChristophK
8
请注意,LocalActivityManager现已过时。 - louiscoquio
4
对我来说,使用选项卡和片段确实很有效。对于那些询问解释的人,我宁愿看到项目及其代码,这很容易理解。是的,“LocalActivityManager”已被弃用,但这只是一个技巧,当他们决定在片段内包含适当的地图支持时,这个问题应该得到妥善解决。 - mdelolmo
显示剩余9条评论

23

正如在Google Groups中所讨论的那样,Peter Doyle构建了一个自定义的兼容库,支持谷歌地图。 android-support-v4-googlemaps

然而,也有一些不足之处:

  

当前的一个缺点是所有扩展FragmentActivity的类都是MapActivitys。 可以制作一个单独的类(即FragmentMapActivity),但这需要对FragmentActivity代码进行一些重构。


6
这也意味着你的应用程序将无法与最新的支持库保持同步。虽然android-support-v4-googlemaps已经更新了多次,但它目前比最新版本落后了2个版本。我同意其他人的观点,认为这个修改过的库比使用已弃用的LocalActivityManager更加难以维护。 - Stan Kurdziel
1
https://github.com/petedoyle/android-support-v4-googlemaps现在已经是最新的版本(截至本消息为止是r10)。我还进行了重构,以便将来更新变得非常容易。 - Pete Doyle
我有一个问题。如果应用程序已经部署和安装,那么“不更新最新的支持库”有什么劣势? - hendrix
这种方法的主要问题在于,你将无法使用可选的地图支持部署你的应用。由于所有的FragmentActivities都继承自MapActivity,因此在没有Google API(例如Kindle Fire)的设备上,它将会崩溃。 - Paul Lammertsma

13

只是为了澄清答案。我尝试了inazaruk和ChristophK建议的方法。实际上,您可以在片段中运行任何活动,而不仅仅是Google Maps。这是实现Google地图活动作为片段的代码,感谢inazaruk和ChristophK。

import com.actionbarsherlock.app.SherlockFragment;
import android.view.Window;

import android.app.LocalActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MapFragment extends SherlockFragment {
    private static final String KEY_STATE_BUNDLE = "localActivityManagerState";

    private LocalActivityManager mLocalActivityManager;

    protected LocalActivityManager getLocalActivityManager() {
        return mLocalActivityManager;
    }

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

        Bundle state = null;
        if (savedInstanceState != null) {
            state = savedInstanceState.getBundle(KEY_STATE_BUNDLE);
        }

        mLocalActivityManager = new LocalActivityManager(getActivity(), true);
        mLocalActivityManager.dispatchCreate(state);
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
            //This is where you specify you activity class
        Intent i = new Intent(getActivity(), GMapActivity.class); 
        Window w = mLocalActivityManager.startActivity("tag", i); 
        View currentView=w.getDecorView(); 
        currentView.setVisibility(View.VISIBLE); 
        currentView.setFocusableInTouchMode(true); 
        ((ViewGroup) currentView).setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        return currentView;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBundle(KEY_STATE_BUNDLE,
                mLocalActivityManager.saveInstanceState());
    }

    @Override
    public void onResume() {
        super.onResume();
        mLocalActivityManager.dispatchResume();
    }

    @Override
    public void onPause() {
        super.onPause();
        mLocalActivityManager.dispatchPause(getActivity().isFinishing());
    }

    @Override
    public void onStop() {
        super.onStop();
        mLocalActivityManager.dispatchStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mLocalActivityManager.dispatchDestroy(getActivity().isFinishing());
    }
}

2
但是唯一避免使用LocalActivityManager的解决方案(如此处所讨论:http://code.google.com/p/android/issues/detail?id=15347)涉及扩展*所有* FragmentActivities,这似乎甚至更不理想。 - Estel
LocalActivityManager已经被弃用,因为ActivityGroup也已经被弃用了。尽管如此,在这种情况下仍然有用,如果人们继续使用它来解决问题,Google可能会重新考虑在未来的版本中将其移除。 - straya

8
截至2012年3月12日,Google发布了 Google Maps Android API v2。现在你可以忘记这些问题了。https://developers.google.com/maps/documentation/android/ 使用新API的示例-https://developers.google.com/maps/documentation/android/start#add_a_map 此API将适用于至少Android API 8,因此请使用它;)。
因此,现在您只需使用“com.google.android.gms.maps.MapFragment”片段类即可在Activity中显示地图。来自上面链接的布局示例:
<?xml version="1.0" encoding="utf-8"?>
<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.MapFragment"/>

4
Google有一个好消息,他们今天发布了一个新的Google Maps API,包括室内地图和MapFragment。 点击这里查看更多信息。
With this new API, adding a map to your Activity is as simple as:

<fragment
  android:id="@+id/map"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  class="com.google.android.gms.maps.MapFragment" />

不完全正确。尝试在一个FragmentPagerAdapter中使用它。 - Thomas Dignan
如果你在谈论黑屏问题,有一个小技巧可以解决它。https://dev59.com/ymYr5IYBdhLWcg3wOX66 - Marcelo
谢谢。我后来看到了,但现在我正在使用带有北极星的第一个地图API版本。 - Thomas Dignan

2

这是一个非常简化的MapFragmentMonoDroidMono for Android)版本:

public class MapFragment : Fragment
{
    // FOLLOW https://dev59.com/5W435IYBdhLWcg3w-lL2
    private static  String KEY_STATE_BUNDLE = "localActivityManagerState";

    public override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        Bundle state = null;
        if (savedInstanceState != null) {
            state = savedInstanceState.GetBundle(KEY_STATE_BUNDLE);
        }
        mLocalActivityManager = new LocalActivityManager(Activity, true);
        mLocalActivityManager.DispatchCreate(state);
    }

    public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        //This is where you specify you activity class
        Intent i = new Intent(Activity, typeof(SteamLocationMapActivity)); 
        Window w = mLocalActivityManager.StartActivity("tag", i); 
        View currentView=w.DecorView; 
        currentView.Visibility = ViewStates.Visible; 
        currentView.FocusableInTouchMode = true; 
        ((ViewGroup) currentView).DescendantFocusability = DescendantFocusability.AfterDescendants;
        return currentView;
    }

    private LocalActivityManager mLocalActivityManager;
    protected LocalActivityManager GetLocalActivityManager() {
        return mLocalActivityManager;
    }   


    public override void OnSaveInstanceState(Bundle outState)
    {
        base.OnSaveInstanceState(outState);
        outState.PutBundle(KEY_STATE_BUNDLE,mLocalActivityManager.SaveInstanceState());
    }

    public override void OnResume()
    {
        base.OnResume();
        mLocalActivityManager.DispatchResume();

    }

    public override void OnPause()
    {
        base.OnPause();
        mLocalActivityManager.DispatchPause(Activity.IsFinishing);
    }

    public override void OnStop()
    {
        base.OnStop();
        mLocalActivityManager.DispatchStop();
    }
}

2
Google Maps API 不是 AOSP 的一部分。只要没有 Google 员工回复,就很难知道未来是否会有 MapFragment。
一个可能的有限替代方案是使用 WebViewFragment 并滥用它来加载一个自定义的 maps.google.com URL。

1
希望Map API能够很快更新,加入片段和矢量绘图功能! - Nathan Schwermann

2
很遗憾,Google还没有回应。如果你真的需要这样做,我找不到其他方法,只能:

让选项卡管理活动从MapActivity继承,以编程方式在其中创建MapView,在mapfragment.xml中包含一个ViewGroup,并使用以下方法将MapView添加到ViewGroup中:

((ViewGroup)getFragmentManager().findFragmentById(R.id.finder_map_fragment).getView()).addView(mapView);;

显然,这与片段自我包含的想法背道而驰,但是……


谢谢,让碎片管理活动继承MapActivity对我来说完美地运作了。 - Pete Doyle

1

1

请问我可以得到解决方案吗:

  1. 创建类TempFragmentActivity扩展MapActivity
  2. 在TempFragmentActivity中有一个MapView对象(像正常定义在xml中一样)
  3. 从父级(LinearLayout)中删除此MapView对象(稍后无效异常)
  4. 将此MapView对象保留在某个位置(例如:TempFragmentActivity的静态成员)
  5. 在您的Fragment中,使用代码(不要在xml中定义)将此MapView对象添加到某个LinearLayout中

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接