Trabalhando com a interface no SDK do Google Maps para Android

Este artigo será útil para aqueles que não usaram anteriormente o SDK do Google Maps em seus trabalhos.

Sob o cortador, são descritos os métodos básicos de trabalho com o mapa, como adicionar e gerenciar marcadores, maneiras de mover a câmera sobre o mapa, controlar o zoom, construir uma rota e geocodificar. Bem como limitações e formas de contorná-las.

imagem
Fonte

Para escrever o artigo, fui inspirado por minha própria experiência, que ganhei ao escrever um pedido de mensageiros usando o Google Maps em seus trabalhos. Portanto, todas as capturas de tela e uma possível menção à lógica de negócios ocorrerão no contexto da criação de uma interface de correio.

Infelizmente, o SDK do Google Maps para Android não permite alterar a posição dos botões de controle, os chamados Os controles da interface do usuário incluem: IndoorLevelPicker - mostrando a planta baixa dos edifícios, Bússola - a bússola, botão Meu local - vá para o mapa no local atual, barra de ferramentas Mapa - botões para construir a rota e abrir o mapa, bem como o ZoomControls - para aumentar e diminuir a escala do mapa.

Usando a barra de ferramentas Mapa e o ZoomControls como exemplo, vamos ver quais dificuldades podem surgir devido à incapacidade de alterar a posição dos controles e como contornar isso.

imagem
Problemas na exibição de controles de interface do usuário do SDK (destacado em laranja) e seus equivalentes personalizados (destacados em verde)

Nesse caso, temos no canto inferior direito um botão (botão de ação flutuante) para ir para a lista de endereços das ordens de entrega. Na imagem à esquerda, você pode ver que o ZoomControls estava embaixo e é praticamente inacessível clicar. Na imagem à direita, quando você clica no marcador, os botões da barra de ferramentas Mapa aparecem, eles também aparecem sob o botão para ir para a lista de pedidos.

Solução

A primeira coisa que precisamos fazer é ocultar a exibição dos botões originais. Você pode fazer isso substituindo o método onMapReady, que é chamado quando o cartão está pronto para uso.

Não mostrar botões O controle de zoom e não mostrar botões criam uma rota a partir do 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); } 


Adicionamos os botões necessários ao layout, onde devem estar de acordo com o nosso design:

imagem
Localização dos botões de controle de mapa personalizados

Em seguida, no método onCreateView, especifique as ações que devem acontecer quando nossos botões são clicados:

Manipuladores dos botões para aumentar e diminuir a escala, além de construir uma rota
 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(); } } }); } 


A peculiaridade do método animateCamera é que a escala muda suavemente, e não instantaneamente, e se, por exemplo, você precisar desativar a animação de um botão de zoom específico quando a escala máxima ou mínima for atingida, precisará redefinir o método onCameraIdle , chamado quando a escala do mapa for alterada.

Ativando e desativando os botões de zoom
  @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); } } 


Para executar qualquer ação com um marcador (exceto arrastar e soltar), por exemplo, crie um novo pedido, exclua um marcador colocado aleatoriamente, vá para um pedido existente ou ligue para o telefone conforme indicado no pedido, adicione os botões de controle apropriados ao layout e registre seus manipuladores.

imagem
Botões de controle de marcador

Processando um clique em um mapa para adicionar um marcador e exibir botões de controle personalizados
 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); } }); } 


Indicamos o que queremos fazer com o marcador quando clicamos no botão Adicionar pedido
 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(); } } }); } 


Outro recurso é que não há botão no SDK para excluir o marcador no cartão. Para fazer isso, também criamos nosso próprio botão:

Especifique o que queremos fazer com o marcador quando clicarmos no botão excluir marcador
 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(); } } }); } 


Ao clicar no marcador, seu título é aberto, clicando no qual também pode ser usado para executar qualquer ação, quando clico no título de um novo marcador, crio um novo pedido para entrega ao correio e, no marcador de um pedido existente, abro informações detalhadas sobre a entrega, incluindo uma lista bens.

Defina a ação ao clicar na janela de informações do marcador
 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()); } } }); } 


O processo de saída de vários marcadores (leia a lista de pedidos) no cartão não é diferente em princípio da saída de um marcador. Um marcador consiste em coordenadas (posição), um título (título), texto pequeno sob um título (snippet) e uma tag (setTag) - ele pode ser usado para identificar muitos marcadores no mapa.

imagem
Vários marcadores de mapa

Desenhando vários marcadores com coordenadas fornecidas
 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); } 


Algumas palavras sobre o geocoder

Um geocoder é usado para obter um endereço com base nas coordenadas. Ao colocar um marcador no mapa e clicar no botão Adicionar pedido, obtemos as coordenadas geográficas do ponto desejado, ou seja, latitude e longitude. Mas, para a conveniência do usuário, será bom mostrar o endereço em formato legível por humanos, ou seja, por exemplo, país, cidade, rua, casa.

O SDK do Google Maps contém a classe Geocoder , chamando seu método getFromLocation , você pode obter uma matriz de endereços nas coordenadas especificadas.

Para não bloquear o encadeamento da interface do usuário por muito tempo, especialmente se a Internet estiver lenta ou inacessível por chamadas - usaremos o RxJava:

imagem
O endereço do ponto resultante no mapa com base nas coordenadas geográficas

Usando o Java RX para acessar o geocoder do Google
 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 -> { }); 


O texto da classe LocationRepostiory na qual ocorre a geocodificação reversa
 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/pt484100/


All Articles