相机和视图

通过简单手势即可倾斜和旋转使用 Maps SDK for Android 制作的地图,从而让用户能够将地图调整到自己所需的方向。由于基于矢量的地图图块占据的空间较小,因此在任何缩放级别下平移地图或更改其角度都几乎不会产生延迟。

代码示例

GitHub 上的 ApiDemos 代码库包含一个展示相机功能的示例:

简介

与网页版 Google 地图类似,Maps SDK for Android 使用墨卡托投影法在设备屏幕(平面)上呈现世界表面(球体)。在东西方向上,由于地球平稳自转,因此地图会不断地重复。在南北方向上,地图覆盖的度数上限大约为南北纬 85 度。

注意:墨卡托投影法在经度方向上的宽度有限,但在纬度方向上的高度无限。我们利用墨卡托投影法在大约 +/-85 度处将基本地图图像截断,使制作出的地图呈方形,从而简化图块选择逻辑。

借助 Maps SDK for Android,您可以通过修改地图的相机来更改用户查看地图的视角。

对相机所做的更改不会使标记、叠加层或您添加的其他图形发生变化,但您可能需要更改所添加的内容,使其更适合新视图。

您可以监听用户在地图上所做的手势,从而更改地图以响应用户请求。例如,回调方法 OnMapClickListener.onMapClick() 可响应地图上的单次点按。通过该方法可收到点按位置的纬度和经度,因此您可以通过平移或缩放至该点做出响应。类似的方法可用于响应标记提示框中的点按操作,或响应标记上的拖动手势。

您还可以监听相机移动,以便您的应用可以在相机开始移动、当前正在移动或停止移动时收到通知。有关详情,请参阅有关相机更改事件的指南。

地图上的 3D 建筑

近距离观看时,很多城市会显示 3D 建筑,如以下加拿大温哥华的图片所示。您可以通过调用 GoogleMap.setBuildingsEnabled(false) 停用 3D 建筑。

加拿大温哥华的地图

相机位置

地图视图建模为一个俯视某个平面的相机。相机的位置(以及地图的渲染)由下列属性指定:目标(纬度/经度位置)方位倾斜角度缩放

相机属性图

目标(位置)

相机目标是地图中心的位置,以纬度和经度坐标形式指定。

方位(方向)

相机方位是地图上的垂直线所指的方向,用从北往顺时针方向偏移的角度(以度为单位)来表示。驾车的用户通常会旋转道路地图使其与他们的行驶方向一致,而远足者使用地图和罗盘时会将地图定向,以使垂直线指向北方。通过 Maps API 可更改地图的对齐方式或方位。例如,使用 90 度方位的结果就是地图向上的方向指向正东方。

倾斜度(视角)

倾斜度定义相机在地图中心位置正上方与地球表面之间的圆弧上所处的位置,用与天底(相机正下方)所呈角度(以度为单位)来表示。更改视角时,地图会以透视法呈现:远处的地图项显示得较小,而近处的地图项显示得较大。以下插图展示了这种情况。

下面的图片中视角为 0 度。第一个图片是视角为 0 度的示意图;位置 1 是相机位置,位置 2 是当前的地图位置。生成的地图显示在该地图的下方。

相机视角为 0 度、缩放级别为 18 的地图的屏幕截图。
以相机的默认视角显示的地图。
显示相机默认位置(在地图位置正上方,角度为 0)的图。
相机的默认视角。

以下图片中的视角为 45 度。请注意,相机并不是倾斜了 45 度,而是沿圆弧移到了正上(0 度)与地面(90 度)中间的位置,也就是位置 3。 相机仍指向地图的中心点,但现在位置 4 处的线表示的区域会显示出来。

相机视角为 45 度、缩放级别为 18 的地图的屏幕截图。
以 45 度视角显示的地图。
显示相机的视角设置为 45 度、缩放级别仍设置为 18 的图。
45 度相机视角。

此屏幕截图中的地图仍以原始地图中的同一点为中心,但地图顶部显示的地图项增加了。当您将角度增加到 45 度以上时,相机和地图位置之间的地图项会按比例显示得较大,而地图位置远处的地图项会按比例显示得较小,从而产生三维效果。

缩放

相机的缩放级别决定了地图的比例。缩放级别越大,屏幕上显示的信息越详细;缩放级别越小,屏幕上显示的世界范围越广。缩放级别为 0 时,地图的比例为:整个世界的宽度约为 256dp(密度无关像素)。

缩放级别每增加 1 个级别,屏幕上世界的宽度就会翻一番。因此,当缩放级别为 N 时,世界的宽度约为 256 * 2N dp,也就是说,缩放级别为 2 时,整个世界的宽度大约为 1024dp。请注意,缩放级别的值不必为整数。地图允许的缩放级别范围取决于多个因素,包括位置、地图类型和屏幕尺寸。超出该范围的任何数值都将转换为最接近的有效值(可以是最小缩放级别或最大缩放级别)。以下列表显示了您在每个缩放级别看到的地图的大致详细程度:

  • 1:世界
  • 5:大陆/洲
  • 10:城市
  • 15:街道
  • 20:建筑物

以下图片显示了不同缩放级别的视觉外观:

缩放级别为 5 的地图的屏幕截图
缩放级别为 5 的地图。
缩放级别为 15 的地图的屏幕截图
缩放级别为 15 的地图。
缩放级别为 20 的地图的屏幕截图
缩放级别为 20 的地图。

注意:受限于屏幕尺寸和密度,一些设备可能不支持最小缩放级别。使用 GoogleMap.getMinimumZoomLevel() 来获取可能的最小地图缩放级别。如果您需要在视口中显示整个世界,最好使用精简模式

移动相机

Maps API 可让您更改在地图上显示世界的哪一部分区域。不必移动地图,更改相机的位置即可达到此目的。

更改相机位置时,可以选择以动画方式呈现相机移动的效果。动画会插入当前相机属性和新的相机属性之间。您还可以控制动画的时长。

如需更改相机位置,您必须利用 CameraUpdate 指定您想将相机移动至何处。借助 Maps API,您可以使用 CameraUpdateFactory 创建多种不同类型的 CameraUpdate。提供的选项如下:

更改缩放级别和设置最小/最大缩放级别

CameraUpdateFactory.zoomIn()CameraUpdateFactory.zoomOut() 可为您提供 CameraUpdate,该类可在保持其他所有属性不变的情况下,按 1.0 的幅度更改缩放级别。

CameraUpdateFactory.zoomTo(float) 可为您提供 CameraUpdate,该类可将缩放级别的值更改为指定值,同时保持所有其他属性不变。

CameraUpdateFactory.zoomBy(float)CameraUpdateFactory.zoomBy(float, Point) 可为您提供 CameraUpdate,该类可按照指定值增加(如果值为负数,则降低)缩放级别。后一种方法可以修正屏幕上的指定点,让该点保留在同一位置(纬度/经度),为实现此操作,相机的位置可能会更改。

您可能会发现设置首选的最小和/或最大缩放级别非常有用。例如,如果您的应用显示地图注点周围的指定区域,或者如果您要将自定义图块叠加层与一组有限的缩放级别搭配使用,此设置有助于掌控用户体验。

Java

private GoogleMap map;
    map.setMinZoomPreference(6.0f);
    map.setMaxZoomPreference(14.0f);
      

Kotlin

private lateinit var map: GoogleMap

    map.setMinZoomPreference(6.0f)
    map.setMaxZoomPreference(14.0f)
      

请注意,出于技术方面的某些考虑,可能会阻止 API 允许用户过低或过高地进行缩放。例如,卫星地图或地形地图图块的最大缩放级别可能低于基本地图图块。

更改相机位置

用户可以通过两种简便的方法执行常见的位置更改操作。CameraUpdateFactory.newLatLng(LatLng) 为您提供 CameraUpdate,该类可更改相机的纬度和经度,同时保持所有其他属性不变。CameraUpdateFactory.newLatLngZoom(LatLng, float) 为您提供 CameraUpdate,该类可更改相机的纬度、经度和缩放级别,同时保持所有其他属性不变。

要以最灵活的方式更改相机位置,请使用 CameraUpdateFactory.newCameraPosition(CameraPosition),该类会为您提供可将相机移至指定位置的 CameraUpdate。用户可直接通过 new CameraPosition() 方法或借助使用 new CameraPosition.Builder() 方法的 CameraPosition.Builder 获取 CameraPosition

平移(滚动)

CameraUpdateFactory.scrollBy(float, float) 为您提供 CameraUpdate,该类可以更改相机的纬度和经度,以使地图按指定的像素数移动。x 正值会将相机向右移动,显示出来的效果就是地图向左移动。y 正值会将相机向下移动,显示出来的效果就是地图向上移动。相反,x 负值会将相机会向左移动,显示出来的效果就是地图向右移动,而 y 负值则会将相机向上移动。滚动方向是相对于相机当前位置而言的。例如,如果相机的方位为 90 度,那么东方即为“上”。

设置边界

设置地图的边界

有时,您可以移动相机,使自己感兴趣的整个区域以尽可能最高的缩放级别显示,这一点非常实用。例如,如果您要显示用户当前所在位置五英里内的所有加油站,则您可能需要移动相机,以使所有加油站都显示在屏幕上。为此,请先计算您想在屏幕上显示的 LatLngBounds。然后使用 CameraUpdateFactory.newLatLngBounds(LatLngBounds bounds, int padding) 获取 CameraUpdate,该类可以更改相机位置,以使指定的 LatLngBounds 完全适合地图。更改相机时需将指定的内边距(以像素为单位)考虑在内。返回的 CameraUpdate 可以确保指定边界和地图边缘之间的间距(以像素为单位)不小于指定的内边距。请注意,地图倾斜度和方位的值都为 0。

Java

LatLngBounds australiaBounds = new LatLngBounds(
    new LatLng(-44, 113), // SW bounds
    new LatLng(-10, 154)  // NE bounds
);
map.moveCamera(CameraUpdateFactory.newLatLngBounds(australiaBounds, 0));
      

Kotlin

val australiaBounds = LatLngBounds(
    LatLng((-44.0), 113.0),  // SW bounds
    LatLng((-10.0), 154.0) // NE bounds
)
map.moveCamera(CameraUpdateFactory.newLatLngBounds(australiaBounds, 0))
      

将地图在给定区域内居中

在某些情况下,您可能希望将相机居中放置在边界内,而不是将极端边界包括在内。例如,在保持固定缩放值的情况下,将相机放置在某个国家/地区的中心位置。在这种情况下,您可以使用类似的方法:创建 LatLngBounds 并将 CameraUpdateFactory.newLatLngZoom(LatLng latLng, float zoom)LatLngBounds.getCenter() 方法结合使用。getCenter() 方法会返回 LatLngBounds 的地理中心。

Java

LatLngBounds australiaBounds = new LatLngBounds(
    new LatLng(-44, 113), // SW bounds
    new LatLng(-10, 154)  // NE bounds
);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(australiaBounds.getCenter(), 10));
      

Kotlin

val australiaBounds = LatLngBounds(
    LatLng((-44.0), 113.0),  // SW bounds
    LatLng((-10.0), 154.0) // NE bounds
)
map.moveCamera(CameraUpdateFactory.newLatLngZoom(australiaBounds.center, 10f))
      

使用方法 newLatLngBounds(boundary, width, height, padding) 的重载,您可以指定矩形的宽度和高度(以像素为单位),并使宽度和高度符合地图的尺寸。系统会对该矩形进行定位,使其中心与地图视图的中心一致(这样,如果指定的尺寸与地图视图的尺寸一致,那么该矩形就会与地图视图一致)。返回的 CameraUpdate 会相应地移动相机,以使指定的 LatLngBounds 在计入所需内边距后,能够在可能的最高缩放级别下在屏幕上的给定矩形内居中显示。

注意:仅当要在设置地图布局后使用 CameraUpdate 移动相机时,才能使用更简单的方法 newLatLngBounds(boundary, padding) 生成该类。在设置布局的过程中,API 会计算地图的显示边界,因为系统需要这些边界来正确投影边界框。相比之下,您可以随时使用由较复杂的方法 newLatLngBounds(boundary, width, height, padding) 返回的 CameraUpdate,此方法甚至可在地图设置好布局之前使用,因为 API 会通过您传递的参数计算显示边界。

将用户的平移限制在给定区域

在上述情况下,您虽然设置了地图的边界,但是用户仍可以滚动或平移至这些边界外。您不妨改为限制地图焦点(相机目标)的纬度/经度中心边界,以便用户只能在这些边界内滚动和平移。例如,购物中心或机场的零售应用可能希望将地图限制到特定边界,使用户只能在这些边界内滚动和平移。

Java

// Create a LatLngBounds that includes the city of Adelaide in Australia.
LatLngBounds adelaideBounds = new LatLngBounds(
    new LatLng(-35.0, 138.58), // SW bounds
    new LatLng(-34.9, 138.61)  // NE bounds
);

// Constrain the camera target to the Adelaide bounds.
map.setLatLngBoundsForCameraTarget(adelaideBounds);
      

Kotlin

// Create a LatLngBounds that includes the city of Adelaide in Australia.
val adelaideBounds = LatLngBounds(
    LatLng(-35.0, 138.58),  // SW bounds
    LatLng(-34.9, 138.61) // NE bounds
)

// Constrain the camera target to the Adelaide bounds.
map.setLatLngBoundsForCameraTarget(adelaideBounds)
      

在下图所示的情况下,相机目标被限制在一个略微大于视口的区域内。只要相机仍然位于边界区域内,用户就可以滚动和平移。叉型记号表示相机目标:

显示大于视口的相机 LatLngBounds 的图片。

地图将始终填满视口,即使这会导致视口显示指定边界以外的区域也是如此。例如,如果您将相机目标定位在边界区域的角上,超出角的区域在视口中仍然可见,但是用户无法滚动到该区域。下图说明了这种情况。叉型记号表示相机目标:

显示相机目标定位在相机 LatLngBounds 右下角的图片。

在下图中,相机目标具有一个非常有限的边界,这让用户基本无法滚动或平移地图。叉型记号表示相机目标:

显示小于视口的相机 LatLngBounds 的图片。

更新相机视图

要为地图应用 CameraUpdate,您可以立即移动相机,或平稳地动态移动相机。要立即使用指定的 CameraUpdate 移动相机,您可以调用 GoogleMap.moveCamera(CameraUpdate)

您可以动画形式呈现所做的更改,让用户享受更愉悦的体验,特别是在短距离移动方面。为此,请不要调用 GoogleMap.moveCamera,而应调用 GoogleMap.animateCamera。地图会平稳地移至新属性。此方法最具体的形式 GoogleMap.animateCamera(cameraUpdate, duration, callback) 提供了以下三种参数:

cameraUpdate
CameraUpdate 用于说明将相机移至哪个位置。
callback
这是一个用于实现 GoogleMap.CancellableCallback 的对象。这个用于处理任务的通用接口定义了 `onCancel()` 和 `onFinished()` 这两种方法。对于动画,您需要在以下情况下调用这些方法:
onFinish()
如果动画完整播放且没有中断,系统会调用该方法。
onCancel()

如果调用 stopAnimation() 造成动画播放中断,或启动了新的相机移动活动,系统会调用该方法。

此外,如果您调用 GoogleMap.stopAnimation(),也会出现上述情况。

duration
动画播放所需的时长,以毫秒为单位,为 int 型数据。

下面的代码段说明了移动相机的一些常用方法。

Java

LatLng sydney = new LatLng(-33.88,151.21);
LatLng mountainView = new LatLng(37.4, -122.1);

// Move the camera instantly to Sydney with a zoom of 15.
map.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 15));

// Zoom in, animating the camera.
map.animateCamera(CameraUpdateFactory.zoomIn());

// Zoom out to zoom level 10, animating with a duration of 2 seconds.
map.animateCamera(CameraUpdateFactory.zoomTo(10), 2000, null);

// Construct a CameraPosition focusing on Mountain View and animate the camera to that position.
CameraPosition cameraPosition = new CameraPosition.Builder()
    .target(mountainView )      // Sets the center of the map to Mountain View
    .zoom(17)                   // Sets the zoom
    .bearing(90)                // Sets the orientation of the camera to east
    .tilt(30)                   // Sets the tilt of the camera to 30 degrees
    .build();                   // Creates a CameraPosition from the builder
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
      

Kotlin

val sydney = LatLng(-33.88, 151.21)
val mountainView = LatLng(37.4, -122.1)

// Move the camera instantly to Sydney with a zoom of 15.
map.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 15f))

// Zoom in, animating the camera.
map.animateCamera(CameraUpdateFactory.zoomIn())

// Zoom out to zoom level 10, animating with a duration of 2 seconds.
map.animateCamera(CameraUpdateFactory.zoomTo(10f), 2000, null)

// Construct a CameraPosition focusing on Mountain View and animate the camera to that position.
val cameraPosition = CameraPosition.Builder()
    .target(mountainView) // Sets the center of the map to Mountain View
    .zoom(17f)            // Sets the zoom
    .bearing(90f)         // Sets the orientation of the camera to east
    .tilt(30f)            // Sets the tilt of the camera to 30 degrees
    .build()              // Creates a CameraPosition from the builder
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))