如何在ViewPager中使用Fragment放置Google Maps V2

144

我想做一个跟Play Store一样的选项卡布局。我已经使用了Androidhive提供的Fragments和ViewPager展示了选项卡布局。但是,我无法在其上实现Google Maps V2。我已经在互联网上搜寻了数小时,但是找不到如何做的教程。请问有没有人可以向我展示如何实现?


6
有趣的是,我不得不回到三年前我提出的问题才能记起如何实现它。 - Jeongbebs
一旦使用了 getChildFragmentManager(),在 ActivityFragment 中实现这个功能并没有太大的区别。 - NecipAllef
13个回答

348

通过使用这段代码,我们可以在任何ViewPager、Fragment或Activity内设置MapView。

在Google Maps最新更新中,仅支持在片段中使用MapView。MapFragment和SupportMapFragment对我来说不起作用。

location_fragment.xml文件中设置显示地图的布局:

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

    <com.google.android.gms.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

现在,我们要设置Java类来显示文件中的地图 MapViewFragment.java

public class MapViewFragment extends Fragment {

    MapView mMapView;
    private GoogleMap googleMap;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.location_fragment, container, false);

        mMapView = (MapView) rootView.findViewById(R.id.mapView);
        mMapView.onCreate(savedInstanceState);

        mMapView.onResume(); // needed to get the map to display immediately

        try {
            MapsInitializer.initialize(getActivity().getApplicationContext());
        } catch (Exception e) {
            e.printStackTrace();
        }

        mMapView.getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(GoogleMap mMap) {
                googleMap = mMap;

                // For showing a move to my location button
                googleMap.setMyLocationEnabled(true);

                // For dropping a marker at a point on the Map
                LatLng sydney = new LatLng(-34, 151);
                googleMap.addMarker(new MarkerOptions().position(sydney).title("Marker Title").snippet("Marker Description"));
            
                // For zooming automatically to the location of the marker
                CameraPosition cameraPosition = new CameraPosition.Builder().target(sydney).zoom(12).build();
                googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
            }
        });

        return rootView;
    }

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

    @Override
    public void onPause() {
        super.onPause();
        mMapView.onPause();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mMapView.onDestroy();
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        mMapView.onLowMemory();
    }
}

最后,您需要通过在Google Cloud控制台注册您的应用程序来获取API密钥。将您的应用程序注册为本机Android应用程序。


2
<uses-library android:name="com.google.android.maps" android:required="true" /> 是用于 Maps V1,而不是 Maps V2。未来会有一些设备没有 com.google.android.maps 固件库,但完全能够显示 Maps V2 地图。在您的清单中添加此行将防止您在这些设备上运行,并且此行不需要 Maps V2 使用。例如,https://github.com/commonsguy/cw-omnibus/tree/master/MapsV2 中的 17 个项目没有此 <uses-library> 元素,但可以正常工作。 - CommonsWare
7
在使用这个方法时,我遇到了错误,请帮忙。 PID: 16260 java.lang.NullPointerException at com.example.imran.maps.MeFragment.setUpMapIfNeeded(MeFragment.java:115) at com.example.imran.maps.MeFragment.onCreateView(MeFragment.java:72)代码如下:googleMap = ((SupportMapFragment) MainActivity.fragmentManager .findFragmentById(R.id.map)).getMap(); - Imran Ahmed
7
我在执行代码时遇到了一个“NullPointerException”错误,出错的代码为mMap = ((SupportMapFragment) MainActivity.fragmentManager.findFragmentById(R.id.location_map)).getMap(); - aletede91
4
如果findFragmentById()返回null,那么getMap()会导致崩溃。下面是示例代码片段: mMap = ((SupportMapFragment) MainActivity.fragmentManager .findFragmentById(R.id.location_map)).getMap(); - Gunnar Forsgren - Mobimation
2
这个不行:java.lang.NullPointerException: 尝试在空对象引用上调用接口方法“void com.google.maps.api.android.lib6.impl.bo.o()” - Peter Weyand
显示剩余32条评论

149

以下方法对我有效。

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

/**
 * A fragment that launches other parts of the demo application.
 */
public class MapFragment extends Fragment {

MapView mMapView;
private GoogleMap googleMap;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    // inflat and return the layout
    View v = inflater.inflate(R.layout.fragment_location_info, container,
            false);
    mMapView = (MapView) v.findViewById(R.id.mapView);
    mMapView.onCreate(savedInstanceState);

    mMapView.onResume();// needed to get the map to display immediately

    try {
        MapsInitializer.initialize(getActivity().getApplicationContext());
    } catch (Exception e) {
        e.printStackTrace();
    }

    googleMap = mMapView.getMap();
    // latitude and longitude
    double latitude = 17.385044;
    double longitude = 78.486671;

    // create marker
    MarkerOptions marker = new MarkerOptions().position(
            new LatLng(latitude, longitude)).title("Hello Maps");

    // Changing marker icon
    marker.icon(BitmapDescriptorFactory
            .defaultMarker(BitmapDescriptorFactory.HUE_ROSE));

    // adding marker
    googleMap.addMarker(marker);
    CameraPosition cameraPosition = new CameraPosition.Builder()
            .target(new LatLng(17.385044, 78.486671)).zoom(12).build();
    googleMap.animateCamera(CameraUpdateFactory
            .newCameraPosition(cameraPosition));

    // Perform any camera updates here
    return v;
}

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

@Override
public void onPause() {
    super.onPause();
    mMapView.onPause();
}

@Override
public void onDestroy() {
    super.onDestroy();
    mMapView.onDestroy();
}

@Override
public void onLowMemory() {
    super.onLowMemory();
    mMapView.onLowMemory();
}
}

fragment_location_info.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.gms.maps.MapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

8
你是个天才,你救了我的命。mMapView.onResume(); //需要立即显示地图的关键。 - Stephane Maarek
1
我一直收到相同的错误java.lang.NullPointerException:IBitmapDescriptorFactory未初始化。我认为尝试捕获会处理这个问题。有人可以帮我吗? - user5016464
@BrandonYang:你知道为什么需要手动调用mMapView的所有生命周期回调函数吗? - Christian Aichinger
6
getMap()е·Іиў«ејғз”ЁгҖӮиҜ·дҪҝз”ЁgetMapAsyncе’ҢonMapReadyCallbackд»Јжӣҝпјҡhttps://dev59.com/questions/zF0Z5IYBdhLWcg3wfgi-#31371953 - jmeinke
我们不需要添加地图API密钥吗? - Faizan Mubasher
显示剩余6条评论

82

如果要在片段中使用 GoogleMap,可以使用以下代码:

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

GoogleMap mGoogleMap = ((SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map)).getMap();


4
谢谢您的回复!所有其他回复都不能与最新的支持库一起使用。 - Kamil Nękanowicz
1
在一个 Fragment 中调用另一个 Fragment 是一种好的实践吗?这会如何影响应用程序性能和良好的软件设计? - AouledIssa

51

最新的做法是使用getMapAsync代替已弃用的方法。

1. 检查清单文件以获取

<meta-data android:name="com.google.android.geo.API_KEY" android:value="xxxxxxxxxxxxxxxxxxxxxxxxx"/>

你可以通过在Google Cloud Console注册你的应用程序来获取API密钥。将你的应用程序注册为Native Android App。

2. 在你的碎片布局.xml中添加FrameLayout(而不是fragment):

                  <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="250dp"
                android:layout_weight="2"
                android:name="com.google.android.gms.maps.SupportMapFragment"
                android:id="@+id/mapwhere" />

或者是任何你想要的高度

3. 在你的Fragment中的onCreateView方法里

    private SupportMapFragment mSupportMapFragment; 

    mSupportMapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapwhere);
    if (mSupportMapFragment == null) {
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        mSupportMapFragment = SupportMapFragment.newInstance();
        fragmentTransaction.replace(R.id.mapwhere, mSupportMapFragment).commit();
    }

    if (mSupportMapFragment != null)
    {
        mSupportMapFragment.getMapAsync(new OnMapReadyCallback() {
            @Override public void onMapReady(GoogleMap googleMap) {
                if (googleMap != null) {

                    googleMap.getUiSettings().setAllGesturesEnabled(true);

                      -> marker_latlng // MAKE THIS WHATEVER YOU WANT

                        CameraPosition cameraPosition = new CameraPosition.Builder().target(marker_latlng).zoom(15.0f).build();
                        CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition);
                        googleMap.moveCamera(cameraUpdate);

                }

            }
        });

3
谢谢,省了很多时间,这是根据最新的库更新所得出的正确答案。 - Sam
对我来说它没有起作用。我花了很多时间才注意到,在布局中应该使用<fragment>而不是FrameLayout - user3448282
在代码中有一个小但重要的错误。添加SupportMapFragment时,应该使用FragmentManager fragmentManager = getChildFragmentManager();而不是getFragmentManager。现在的代码中,当onCreateView被调用时,地图片段总是会被添加(这很糟糕,因为它不能保持地图状态)。此外,我建议只在mSupportmapFragment == null分支中调用getMapAsync以执行初始设置一次! - user1299412
@user1299412,你可以编辑帖子,我会更新答案的。 :) - OWADVL
当我在活动中将SupportMapFragment更改为MapFragment,将布局文件中的FrameLayout更改为fragment并将com.google.android.gms.maps.SupportMapFragment更改为com.google.android.gms.maps.MapFragment时,它对我很好用。 在这些更改之前,它无法正常工作。也许这可以帮助其他人.... - CodeNinja
明天试试这个答案,似乎没有使用getMap()是最好的选择。非常感谢! - Johannes Knust

10

以下是我详细完成的步骤:

从这里获取Google地图API密钥

另一种简单的方法:

首先登录您的Google账户,访问Google库并选择Google Maps Android API

在Android Studio默认的地图活动中发现依赖关系:

compile 'com.google.android.gms:play-services:10.0.1'

请将您的密钥放入AndroidManifest文件中的应用程序下,代码如下:

在AndroidManifest.xml中进行以下更改:

    // required permission
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />


        // google map api key put under/inside <application></application>
        // android:value="YOUR API KEY"
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="AIzasdfasdf645asd4f847sad5f45asdf7845" />

片段代码:

public class MainBranchFragment extends Fragment implements OnMapReadyCallback{

private GoogleMap mMap;
    public MainBranchFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view= inflater.inflate(R.layout.fragment_main_branch, container, false);
        SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.main_branch_map);
        mapFragment.getMapAsync(this);
        return view;
    }




     @Override
        public void onMapReady(GoogleMap googleMap) {
            mMap = googleMap;
            LatLng UCA = new LatLng(-34, 151);
            mMap.addMarker(new MarkerOptions().position(UCA).title("YOUR TITLE")).showInfoWindow();

            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(UCA,17));

        }
    }

在你的片段XML中:

<fragment
                android:id="@+id/main_branch_map"
                android:name="com.google.android.gms.maps.SupportMapFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context="com.googlemap.googlemap.MapsActivity" />

gMapFragment.getMapAsync 在 getMapAsync 上存在未解决的引用。这样是行不通的。 - Peter Weyand
你在活动(Activity)还是片段(Fragment)中? - dharmx
这个可以用!但我不确定在一个片段中调用另一个片段是否是一个好的做法!请建议。 - AouledIssa

5

如果在更改FragmentTabHost中的标签时出现NullPointerException问题,您只需要将以下代码添加到初始化选项卡的类(即具有TabHost的类)中即可解决该问题。这是代码:

/**** Fix for error : Activity has been destroyed, when using Nested tabs 
 * We are actually detaching this tab fragment from the `ChildFragmentManager`
 * so that when this inner tab is viewed back then the fragment is attached again****/

import java.lang.reflect.Field;

@Override
public void onDetach() {
    super.onDetach();
    try {
        Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
        childFragmentManager.setAccessible(true);
        childFragmentManager.set(this, null);
    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

在类型“Field”上应该导入什么?有许多可能性。 - Jeongbebs
我应该把我的项目发送给你吗?然后告诉我该怎么做? - Jeongbebs
项目已发送至您的电子邮件。 - Jeongbebs
它仍然强制停止。 :( - Jeongbebs
1
这个 bug 正在 Android 开源问题跟踪器中被追踪:https://code.google.com/p/android/issues/detail?id=42601 - Kristopher Johnson
显示剩余2条评论

4
public class DemoFragment extends Fragment {


MapView mapView;
GoogleMap map;
LatLng CENTER = null;

public LocationManager locationManager;

double longitudeDouble;
double latitudeDouble;

String snippet;
String title;
Location location;
String myAddress;

String LocationId;
String CityName;
String imageURL;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    View view = inflater
                .inflate(R.layout.fragment_layout, container, false);

    mapView = (MapView) view.findViewById(R.id.mapView);
        mapView.onCreate(savedInstanceState);

  setMapView();


 }

 private void setMapView() {
    try {
        MapsInitializer.initialize(getActivity());

        switch (GooglePlayServicesUtil
                .isGooglePlayServicesAvailable(getActivity())) {
        case ConnectionResult.SUCCESS:
            // Toast.makeText(getActivity(), "SUCCESS", Toast.LENGTH_SHORT)
            // .show();

            // Gets to GoogleMap from the MapView and does initialization
            // stuff
            if (mapView != null) {

                locationManager = ((LocationManager) getActivity()
                        .getSystemService(Context.LOCATION_SERVICE));

                Boolean localBoolean = Boolean.valueOf(locationManager
                        .isProviderEnabled("network"));

                if (localBoolean.booleanValue()) {

                    CENTER = new LatLng(latitude, longitude);

                } else {

                }
                map = mapView.getMap();
                if (map == null) {

                    Log.d("", "Map Fragment Not Found or no Map in it!!");

                }

                map.clear();
                try {
                    map.addMarker(new MarkerOptions().position(CENTER)
                            .title(CityName).snippet(""));
                } catch (Exception e) {
                    e.printStackTrace();
                }

                map.setIndoorEnabled(true);
                map.setMyLocationEnabled(true);
                map.moveCamera(CameraUpdateFactory.zoomTo(5));
                if (CENTER != null) {
                    map.animateCamera(
                            CameraUpdateFactory.newLatLng(CENTER), 1750,
                            null);
                }
                // add circle
                CircleOptions circle = new CircleOptions();
                circle.center(CENTER).fillColor(Color.BLUE).radius(10);
                map.addCircle(circle);
                map.setMapType(GoogleMap.MAP_TYPE_NORMAL);

            }
            break;
        case ConnectionResult.SERVICE_MISSING:

            break;
        case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:

            break;
        default:

        }
    } catch (Exception e) {

    }

}

在fragment_layout中。
<com.google.android.gms.maps.MapView
                android:id="@+id/mapView"
                android:layout_width="match_parent"
                android:layout_height="160dp"                    
                android:layout_marginRight="10dp" />

有没有办法将地图片段移动到另一个片段后面,例如菜单片段?它在某种程度上会刷新并将我的菜单片段发送到后面,而不是保持自己的位置。 - sitilge
你实际想要做什么? - Vaishali Sutariya

3
当您添加地图时,请使用以下内容:
getChildFragmentManager().beginTransaction()
    .replace(R.id.menu_map_container, mapFragment, "f" + shabbatIndex).commit();

使用.add代替,而不是使用getFragmentManager


3
这是 Kotlin 的实现方式:
fragment_map.xml文件中,你应该有:
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

在你的 MapFragment.kt 中应该有:
    private fun setupMap() {
        (childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?)!!.getMapAsync(this)
    }

onCreateView中调用setupMap()函数。

1

我刚刚创建了MapActivity并将其填充到片段中。 MapActivity.java:

package com.example.ahmedsamra.mansouratourguideapp;

import android.support.v4.app.FragmentActivity;
import android.os.Bundle;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

    private GoogleMap mMap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_categories);//layout for container
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.container, new MapFragment())
                .commit();
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }


    /**
     * Manipulates the map once available.
     * This callback is triggered when the map is ready to be used.
     * This is where we can add markers or lines, add listeners or move the camera. In this case,
     * we just add a marker near Sydney, Australia.
     * If Google Play services is not installed on the device, the user will be prompted to install
     * it inside the SupportMapFragment. This method will only be triggered once the user has
     * installed Google Play services and returned to the app.
     */
    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        // Add a marker in Sydney and move the camera
        LatLng mansoura = new LatLng(31.037933, 31.381523);
        mMap.addMarker(new MarkerOptions().position(mansoura).title("Marker in mansoura"));
        mMap.moveCamera(CameraUpdateFactory.newLatLng(mansoura));
    }
}

activity_map.xml:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.ahmedsamra.mansouratourguideapp.MapsActivity" />

MapFragment.java:-

package com.example.ahmedsamra.mansouratourguideapp;


import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
 * A simple {@link Fragment} subclass.
 */
public class MapFragment extends Fragment {


    public MapFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.activity_maps,container,false);
        return rootView;
    }

}

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