介绍
对用户来说最有用的功能之一就是地图集成。 在本系列的前一部分中 ,我们讨论了如何使用Google Developer Console设置Android版Google Maps以及如何创建基本的Google Maps片段。 然后,我们继续添加各种标记以及如何在地图上绘制。
在本教程中,您将扩展在上一篇文章中学到的知识,以便将视图放置在地图顶部,覆盖室内水平选择器控件以及向您的应用程序添加街景组件。 可以在GitHub上找到本文的源代码。
1.设置
首先,请按照本系列上一篇文章中列出的步骤操作,以使用MapFragment
创建一个基本项目,将其附加到Activity
,然后通过Google Developers Console激活Google Maps API。 对于本教程,您不需要使用位置Play Services类,但是需要将地图Play Services库导入到build.gradle dependencies
节点中。
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.0'
compile 'com.google.android.gms:play-services-maps:7.8.0'
}
完成后,您将看到一个类似于以下内容的屏幕:
![初始地图视图](https://i-blog.csdnimg.cn/blog_migrate/534c77fa9c24b213dd7a961a574fef80.png)
接下来,您需要设置相机。 在本教程中,我们将重点介绍纽约市的麦迪逊广场花园,因为它是使用室内水平图的建筑物的绝佳示例。
在onViewCreated
,可以将调用添加到以下帮助器方法initCamera
。 您可能还记得我们需要等到onViewCreated
才能使用Google Maps,因为这是我们知道可以使用该地图对象的时候。
private void initCamera() {
CameraPosition position = CameraPosition.builder()
.target( new LatLng( 40.7506, -73.9936 ) )
.zoom( 18f )
.bearing( 0.0f )
.tilt( 0.0f )
.build();
getMap().animateCamera(
CameraUpdateFactory.newCameraPosition( position ), null );
getMap().setMapType( GoogleMap.MAP_TYPE_HYBRID );
}
上面的方法将相机移到我们的目标并放大到足以使室内选择器可见的程度。 您会注意到,在屏幕的右侧有一条数字,并且在每一层的地图上都有一个叠加层。 当您在右侧选择其他级别时,当前平面图会设置为新的平面动画。 这是您稍后将使用的功能,以便拥有自己的视图控件级别选择。
![默认室内液位选择器的示例](https://i-blog.csdnimg.cn/blog_migrate/9f3da70be6ed71e3a8a62b47ac2c288d.png)
接下来,您需要实现将在本教程中使用的三个接口。
-
GoogleMap.OnIndoorStateChangeListener
用于确定室内水平选择器何时更改了可见性。 -
SeekBar.OnSeekBarChangeListener
与我们的视图叠加层一起使用以控制级别选择,而不是使用右侧的默认按钮集。 - 在此示例中,使用
GoogleMap.OnMapLongClickListener
更改街景组件的显示位置。
public class MapFragment extends SupportMapFragment implements
GoogleMap.OnIndoorStateChangeListener,
GoogleMap.OnMapLongClickListener,
SeekBar.OnSeekBarChangeListener {
为这三个界面添加所需的方法后,即可开始在地图顶部添加视图。
2.叠加视图
尽管Google Maps的基本功能可以满足大多数需求,但有时您还是希望在地图上添加其他视图以执行操作。 在本教程中,我们将添加一个SeekBar
和一些TextView
对象,以自定义室内水平选择器的控件。
首先创建一个新的XML布局文件view_map_overlay.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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/indoor_min_level"
android:text="0"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="4dp"
android:textSize="20sp"
android:gravity="center"
android:textColor="@android:color/white" />
<SeekBar
android:id="@+id/indoor_level_selector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="8" />
<TextView
android:id="@+id/indoor_max_level"
android:text="10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="4dp"
android:textSize="20sp"
android:textColor="@android:color/white"
android:gravity="center" />
</LinearLayout>
</RelativeLayout>
布局文件完成后,您可以将其作为叠加层添加到地图片段中。 在onCreateView
,您需要访问ViewGroup
父级,将新的布局叠加层膨胀,并将其附加到父级。 在这里,您还可以保存对叠加层中每个视图的引用,以便以后可以在您的应用中对其进行更改。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup parent = (ViewGroup) super.onCreateView( inflater, container, savedInstanceState );
View overlay = inflater.inflate( R.layout.view_map_overlay, parent, false );
mIndoorSelector = (SeekBar) overlay.findViewById( R.id.indoor_level_selector );
mIndoorMinLevel = (TextView) overlay.findViewById( R.id.indoor_min_level );
mIndoorMaxLevel = (TextView) overlay.findViewById( R.id.indoor_max_level );
parent.addView( overlay );
return parent;
}
运行应用程序时,您应该在地图顶部看到您的视图。 但是,您仍然会看到默认的级别选择器,该视图会使视图变得混乱。
![移除默认室内水平选择器控件之前的覆盖](https://i-blog.csdnimg.cn/blog_migrate/62ca87df67091b1d098a4ef3cb83fea3.png)
为了解决此问题,请创建一个名为initMapIndoorSelector
的新方法,然后从onViewCreated
调用。 它所需要做的就是为SeekBar
和室内水平更改设置侦听器,以及禁用默认的室内水平选择器。
private void initMapIndoorSelector() {
mIndoorSelector.setOnSeekBarChangeListener( this );
getMap().getUiSettings().setIndoorLevelPickerEnabled( false );
getMap().setOnIndoorStateChangeListener( this );
}
现在,您的视图已覆盖在地图上,您必须将其隐藏,直到需要它为止。 在onViewCreated
,调用一个名为hideFloorLevelSelector
的新帮助程序方法,该方法将隐藏所有覆盖的视图。
private void hideFloorLevelSelector() {
mIndoorSelector.setVisibility( View.GONE );
mIndoorMaxLevel.setVisibility( View.GONE );
mIndoorMinLevel.setVisibility( View.GONE );
}
3.使用室内液位选择器
创建并隐藏视图后,您可以开始添加逻辑以使视图在需要时显示并与地图进行交互。 之前,您创建了onIndoorBuildingFocused
方法作为GoogleMap.OnIndoorStateChangeListener
的一部分。 在该方法中,您需要保存对焦点所在建筑物的引用,然后在必要时隐藏或显示SeekBar
控件。
@Override
public void onIndoorBuildingFocused() {
mIndoorBuilding = getMap().getFocusedBuilding();
if( mIndoorBuilding == null || mIndoorBuilding.getLevels() == null || mIndoorBuilding.getLevels().size() <= 1 ) {
hideFloorLevelSelector();
} else {
showFloorLevelSelector();
}
}
当建筑物对地图摄像头可见并且地图放大足够时,室内建筑物将获得焦点。 如果不再满足这些条件,则将再次调用此方法,并且getMap().getFocusedBuilding
将返回null
值。
showFloorLevelSelector
使所有覆盖的视图均可见,将SeekBar
移至适当的选定值,并将文本标签设置为代表该建筑物顶层和底层的简称的值。 从IndoorBuilding
对象检索级别时, IndoorBuilding
是列表中的最后一项,最高层位于位置0 。
private void showFloorLevelSelector() {
if( mIndoorBuilding == null )
return;
int numOfLevels = mIndoorBuilding.getLevels().size();
mIndoorSelector.setMax( numOfLevels - 1 );
//Bottom floor is the last item in the list, top floor is the first
mIndoorMaxLevel.setText( mIndoorBuilding.getLevels().get( 0 ).getShortName() );
mIndoorMinLevel.setText( mIndoorBuilding.getLevels().get( numOfLevels - 1 ).getShortName() );
mIndoorSelector.setProgress( mIndoorBuilding.getActiveLevelIndex() );
mIndoorSelector.setVisibility( View.VISIBLE );
mIndoorMaxLevel.setVisibility( View.VISIBLE );
mIndoorMinLevel.setVisibility( View.VISIBLE );
}
您需要为室内水平选择器实现的最终方法是onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
。 更改SeekBar
位置后,您需要在当前建筑物上激活一个新级别。 由于级别是从上到下排序的,因此您需要在位置numOfLevels - 1 - progress
上激活级别,以便与SeekBar
的位置相关联。
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
if( mIndoorBuilding == null )
return;
int numOfLevels = mIndoorBuilding.getLevels().size();
mIndoorBuilding.getLevels().get( numOfLevels - 1 - progress ).activate();
}
4.添加街景
既然您知道如何在地图上叠加视图以及如何使用室内水平选择器,那么让我们跳入如何在应用程序中使用街景视图。 与Google地图一样,街景视图可让您使用片段或视图。 对于此示例,您将使用StreetViewPanoramaView
并将其覆盖到MapFragment
。
该视图将被初始化为显示麦迪逊广场花园旁边的街道,并且当您长按地图的其他区域时,街景视图将显示与所选位置关联的图像。 如果您选择显示未直接与街景图像相连的区域,则Google会选择距离显示范围最近的显示区域。 如果附近没有街景视图图像(例如您选择了大海中的某个位置),则街景视图将显示黑屏。
还有一点需要注意的是,您一次只能向用户显示一个StreetViewPanoramaView
或片段。
![麦迪逊广场花园街景](https://i-blog.csdnimg.cn/blog_migrate/c77e442872b1c3402bba62175900af19.png)
首先,更新view_map_overlay.xml以添加StreetViewPanoramaView
。
<com.google.android.gms.maps.StreetViewPanoramaView
android:id="@+id/steet_view_panorama"
android:layout_width="match_parent"
android:layout_height="240dp"
android:layout_alignParentBottom="true"/>
准备好布局文件后,进入MapFragment
onCreateView
,保存对新视图的引用,然后为该视图调用onCreate
方法。 调用onCreate
很重要,因为在附加此视图之前已经调用了当前片段的onCreate
,并且街景视图组件在onCreate
中执行初始化所需的操作。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup parent = (ViewGroup) super.onCreateView( inflater, container, savedInstanceState );
View overlay = inflater.inflate( R.layout.view_map_overlay, parent, false );
mIndoorSelector = (SeekBar) overlay.findViewById( R.id.indoor_level_selector );
mIndoorMinLevel = (TextView) overlay.findViewById( R.id.indoor_min_level );
mIndoorMaxLevel = (TextView) overlay.findViewById( R.id.indoor_max_level );
mStreetViewPanoramaView = (StreetViewPanoramaView) overlay.findViewById(R.id.steet_view_panorama);
mStreetViewPanoramaView.onCreate(savedInstanceState);
parent.addView(overlay);
return parent;
}
接下来,在onViewCreated
,添加一个名为initStreetView
的新方法。 此新方法准备就绪后将异步获取StreetViewPanorama
对象,并处理显示您的初始街景位置。 请注意,只能从主线程调用getStreetViewPanoramaAsync( OnStreetViewPanoramaReadyCallback callback )
,这一点很重要。
private void initStreetView() {
getMap().setOnMapLongClickListener( this );
mStreetViewPanoramaView.getStreetViewPanoramaAsync(new OnStreetViewPanoramaReadyCallback() {
@Override
public void onStreetViewPanoramaReady(StreetViewPanorama panorama) {
mPanorama = panorama;
showStreetView( new LatLng( 40.7506, -73.9936 ) );
}
});
}
最后,您需要定义上面显示的showStreetView( LatLng latlng )
帮助方法。 此方法创建一个StreetViewPanoramaCamera
对象,该对象使您可以更改街景相机的倾斜,缩放和方位。 对于此示例,相机设置为默认值。
接下来,您需要设置相机位置。 在此示例中,我们还打开了一个可选设置以显示街道名称。
private void showStreetView( LatLng latLng ) {
if( mPanorama == null )
return;
StreetViewPanoramaCamera.Builder builder = new StreetViewPanoramaCamera.Builder( mPanorama.getPanoramaCamera() );
builder.tilt( 0.0f );
builder.zoom( 0.0f );
builder.bearing( 0.0f );
mPanorama.animateTo( builder.build(), 0 );
mPanorama.setPosition( latLng, 300 );
mPanorama.setStreetNamesEnabled( true );
}
一旦您的showStreetView( LatLng latlng )
方法完成,也可以从onMapLongClick(LatLng latLng)
调用它,因此您可以轻松更改正在显示的区域。
@Override
public void onMapLongClick(LatLng latLng) {
showStreetView( latLng );
}
![街景位置已由onMapLongClickLatLng latLng更改](https://i-blog.csdnimg.cn/blog_migrate/59df777a2ddfa26a75dbfceda9786f5f.png)
结论
在本教程中,您通过向MapFragment
添加其他视图学习了一些与Google Maps交互的高级方法,并学习了如何控制室内建筑物级别选择器。 我们还介绍了将街景功能添加到您的应用程序的基本知识,以便为用户显示不同的观点。
在本系列的下一部分中,您将了解Google Maps Utilities库以及如何使用它为应用程序添加标记群集,热图和其他有用的功能。