使用Android版Google Maps SDK中的界面

本文对以前未使用过Google Maps SDK的用户很有用。

在裁切器下,描述了处理地图的基本方法,例如添加和管理标记,将相机移动到地图上,控制缩放,构建路线和进行地理编码的方法。 以及限制和规避它们的方法。

图片
来源

写这篇文章时,我受到了自己的经验的启发,这是我在使用快递公司的工作人员使用Google地图编写应用程序时获得的。 因此,所有屏幕截图和可能提及的业务逻辑都将在构建快递员界面的情况下发生。

不幸的是,Android版Google Maps SDK不允许您更改控制按钮的位置,即所谓的 UI控件包括:IndoorLevelPicker-显示建筑物的平面图,Compass-指南针,我的位置按钮-转到当前位置的地图,Map工具栏-用于构建路线和打开地图的按钮,以及ZoomControls-增大或减小地图比例。

以“地图”工具栏和“缩放控件”为例,让我们看看由于无法更改控件的位置以及如何解决此问题而可能会遇到哪些困难。

图片
从SDK(以橙色突出显示)和自定义对应项(以绿色突出显示)显示UI控件时出现问题

在这种情况下,我们在右下角有一个按钮(浮动操作按钮),用于转到交货单的地址列表,在左图中,您可以看到ZoomControls在其下方,几乎无法单击。 在右侧的图片中,当您单击标记时,将显示“地图”工具栏中的按钮,这些按钮也出现在按钮下方,以转到订单列表。

解决方案

我们需要做的第一件事是隐藏原始按钮的显示。 您可以通过重写onMapReady方法来实现此目的,该方法在卡片准备就绪时可以调用。

不显示按钮缩放控件,也不显示按钮从SDK构建路线
private GoogleMap mMap; private UiSettings uiSettings; @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; uiSettings = mMap.getUiSettings(); //   Zoom uiSettings.setZoomControlsEnabled(false); //         SDK uiSettings.setMapToolbarEnabled(false); } 


我们在布局中添加了必要的按钮,这些按钮应与我们的设计保持一致:

图片
自定义地图控件按钮的位置

然后,在onCreateView方法中,指定单击我们的按钮时应该执行的操作:

用于增加和减小比例以及构建路线的按钮的处理程序
 private ImageButton imageButtonZoomIn; private ImageButton imageButtonZoomOut; private ImageButton imageButtonRoute; private GoogleMap mMap; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { //  imageButtonZoomIn = view.findViewById(R.id.imageButtonZoomIn); imageButtonZoomIn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mMap.animateCamera(CameraUpdateFactory.zoomIn()); } }); //  imageButtonZoomOut = view.findViewById(R.id.imageButtonZoomOut); imageButtonZoomOut.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mMap.animateCamera(CameraUpdateFactory.zoomOut()); } }); //      imageButtonRoute = view.findViewById(R.id.imageButtonRoute); imageButtonRoute.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String latitude = String.valueOf(activMarker.getPosition().latitude); String longitude = String.valueOf(activMarker.getPosition().longitude); Uri gmmIntentUri = Uri.parse("google.navigation:q=" + latitude + "," + longitude); Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); mapIntent.setPackage("com.google.android.apps.maps"); try{ if (mapIntent.resolveActivity(Objects.requireNonNull(getActivity()).getPackageManager()) != null) { startActivity(mapIntent); } }catch (NullPointerException e){ Log.e(TAG, "onClick: NullPointerException: Couldn't open map." + e.getMessage() ); Toast.makeText(getActivity(), "Couldn't open map", Toast.LENGTH_SHORT).show(); } } }); } 


animateCamera方法的独特之处在于比例尺平滑变化而不是立即变化,例如,如果需要在达到最大或最小比例尺时关闭特定缩放按钮的动画,则需要重新定义onCameraIdle方法,当地图比例尺更改时会调用该方法。

激活和停用缩放按钮
  @Override public void onCameraIdle() { if (mMap.getCameraPosition().zoom == mMap.getMinZoomLevel()){ //  ,     imageButtonZoomOut.setEnabled(false); imageButtonZoomIn.setEnabled(true); }else if (mMap.getCameraPosition().zoom == mMap.getMaxZoomLevel()){ //  ,     imageButtonZoomOut.setEnabled(true); imageButtonZoomIn.setEnabled(false); }else { //       imageButtonZoomOut.setEnabled(true); imageButtonZoomIn.setEnabled(true); } } 


要使用标记执行任何操作(拖放操作除外),例如,创建新订单,删除随机放置的标记,转到现有订单或按该订单指示拨打电话,在布局中添加适当的控制按钮并注册其处理程序。

图片
标记控制按钮

处理对地图的单击以添加标记并显示自定义控件按钮
 private GoogleMap mMap; private ImageButton imageButtonRoute; @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() { @Override public void onMapClick(LatLng latLng) { imageButtonRoute.setVisibility(View.GONE); if (myMarker !=null){ //   myMarker.remove(); } //    myMarker = mMap.addMarker(new MarkerOptions() .position(latLng) //   .title(Objects.requireNonNull(getContext()).getString(R.string.title_on_marker_to_new_order)) // true ,         .draggable(true)); myMarker.setTag(null); //    imageButtonAddMarker.setVisibility(View.VISIBLE); imageButtonRemoveMarker.setVisibility(View.VISIBLE); } }); } 


当我们点击添加订单按钮时​​,我们会指出我们要对标记进行的操作
 private ImageButton imageButtonAddMarker; private Marker myMarker; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { imageButtonAddMarker = view.findViewById(R.id.imageButtonAddMarker); imageButtonAddMarker.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myMarker !=null && myMarker.getTag()==null) { //    imageButtonAddMarker.setVisibility(View.GONE); imageButtonRemoveMarker.setVisibility(View.GONE); //  ,          listener.openOrderContentsFragmentFromMap(null, myMarker); //   ,     myMarker.remove(); } } }); } 


另一个功能是,SDK中没有按钮可以删除卡上的标记。 为此,我们还制作了自己的按钮:

单击“删除标记”按钮时,指定要对标记执行的操作
 private ImageButton imageButtonRemoveMarker; private Marker myMarker; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { imageButtonRemoveMarker = view.findViewById(R.id.imageButtonRemoveMarker); imageButtonRemoveMarker.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myMarker !=null && myMarker.getTag()==null){ //   imageButtonAddMarker.setVisibility(View.GONE); imageButtonRemoveMarker.setVisibility(View.GONE); //    myMarker.remove(); } } }); } 


单击标记时,其标题将打开,单击该标记也可以执行任何操作,当我单击新标记的标题时,我会创建一个新订单将其交付给快递员,并在现有订单的标记上打开详细的交货信息,包括清单货物。

单击标记信息窗口时设置操作
 private GoogleMap mMap; @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; mMap.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() { @Override public void onInfoWindowClick(Marker marker) { if (marker.getTag()==null) { //    : //      listener.openOrderContentsFragmentFromMap(null, marker); if (myMarker != null) { //   ,     myMarker.remove(); } } else { //     : //         listener.openOrderContentsFragment((Long) marker.getTag()); } } }); } 


将多个标记(读取订单列表)输出到卡的过程在原理上与输出一个标记没有什么不同。 标记由坐标(位置),标题(标题),标题下的小文本(摘要)和标签(setTag)组成-可用于在地图上标识许多标记。

图片
几个地图标记

使用给定坐标绘制多个标记
 public void drawListMarker(List<InfoMarker> latLngList) { if (latLngList == null || latLngList.size() == 0) { return; } //    mMap.clear(); LatLngBounds.Builder builder = new LatLngBounds.Builder(); boolean fiarstGreean = true; int count = 1; for (InfoMarker latLng : latLngList) { BitmapDescriptor icon; if (fiarstGreean){ //      //..           icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN); fiarstGreean = false; } else { icon = latLng.getIcon(); } //      mMap.addMarker(new MarkerOptions().position(latLng.getLatLng()).title(String.valueOf(count)).snippet(latLng.getTitle()).icon(icon)).setTag(latLng.getIdOrder()); builder.include(latLng.getLatLng()); count++; } //       CameraUpdate cameraUpdate; if (loaded) { cameraUpdate = CameraUpdateFactory .newLatLngBounds(builder.build(), 100); } else { cameraUpdate = CameraUpdateFactory.newLatLng(builder.build().getCenter()); } mMap.moveCamera(cameraUpdate); mMap.animateCamera(CameraUpdateFactory.zoomIn()); mMap.animateCamera(CameraUpdateFactory.zoomTo(10), 1000, null); } 


关于地理编码器的几句话

地理编码器用于根据坐标获取地址。 在地图上放置一个标记,然后点击添加订单按钮,即可获得所需点的地理坐标,即 纬度和经度。 但是为了方便用户,最好以人类可读的形式显示地址,例如国家,城市,街道,房屋。

Google Maps SDK包含Geocoder类,通过调用其getFromLocation方法, 可以获取指定坐标处的地址数组。

为了避免长时间阻塞UI线程(尤其是在Internet速度较慢或无法访问时),我们将使用RxJava:

图片
根据地理坐标在地图上生成的点地址

使用Java RX访问Google Geocoder
 LatLng position = myMarker.getPosition(); Location location = new Location("new"); location.setLatitude(position.latitude); location.setLongitude(position.longitude); LocationRepostiory locationRepostiory = new LocationRepostiory(context, location); locationRepostiory.getLastLocation(). observeOn(SchedulerProvider.getInstance().ui()). subscribeOn(SchedulerProvider.getInstance().computation()). subscribe(locationString -> { if(editTextAddress.length()==0){ //       ,      editTextAddress.setText(locationString); } }, throwable -> { }); 


发生反向地理编码的LocationRepostiory类的文本
 public class LocationRepostiory { private Context context; private Location location; public LocationRepostiory(Context context, Location location) { this.context = context; this.location = location; } public Single<String> getLastLocation() { return Single.create(this::subscribeOnLocation); } private void subscribeOnLocation(SingleEmitter<String> e) { Geocoder geocoder = new Geocoder(context, Locale.getDefault()); String errorMessage = ""; List<Address> addresses = null; try { //     addresses = geocoder.getFromLocation( location.getLatitude(), location.getLongitude(), // In this sample, get just a single address. 1); } catch (IOException ioException) { //,      I/O. errorMessage = context.getString(R.string.service_not_available); Log.e(TAG, errorMessage, ioException); } catch (IllegalArgumentException illegalArgumentException) { // ,       errorMessage = context.getString(R.string.invalid_lat_long_used); Log.e(TAG, errorMessage + ". " + "Latitude = " + location.getLatitude() + ", Longitude = " + location.getLongitude(), illegalArgumentException); } //  ,     if (addresses == null || addresses.size() == 0) { if (errorMessage.isEmpty()) { errorMessage = context.getString(R.string.no_address_found); Log.e(TAG, errorMessage); } } else { Address address = addresses.get(0); ArrayList<String> addressFragments = new ArrayList<String>(); //      . for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) { e.onSuccess(address.getAddressLine(i)); } } } } 

Source: https://habr.com/ru/post/zh-CN484100/


All Articles