2 komentarze

Google Maps – przegląd możliwości biblioteki

Sierpień 15, 2011 Biblioteki zewnętrzne Tutoriale

Wpis ten jest rozwinięciem informacji o bibliotece Google Maps dla systemu Android. Ostatnio zapoznaliśmy się z informacjami na temat tego jak dołączyć i wykorzystać mapy w naszej aplikacji, dziś natomiast przyglądniemy się niektórym możliwościom, jakie dostarcza nam ta biblioteka.

Przygotowanie

Zaczniemy od zbudowania prostego layoutu, którego wygląd prezentuje się w ten sposób (zrzut z edytora graficznego):

Jak widać, komponent MapView wypełnia cały ekran. Efekt zawierania się przycisków oraz checkboxów osiągnąłem za pomocą upakowania wszystkiego w kontener RelativeLayout (sama kontrolka MapView nie może posiadać żadnych elementów wewnątrz siebie). Poniżej kompletny kod źródłowy pliku main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<com.google.android.maps.MapView
		xmlns:android="http://schemas.android.com/apk/res/android"
		android:id="@+id/mapview"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:enabled="true"
		android:clickable="true"
		android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
	<LinearLayout
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:id="@+id/LinearLayout01"
		android:orientation="vertical"
		android:gravity="right"
		android:layout_alignParentRight="true"
		android:layout_margin="10dp"
		android:padding="5dp"
		android:background="#44000000">
		<CheckBox
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="Satellite"
			android:layout_gravity="left"
			android:id="@+id/cbSatellite" />
		<CheckBox
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="Street"
			android:layout_gravity="left"
			android:id="@+id/cbStreet" />
		<CheckBox
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="Traffic"
			android:layout_gravity="left"
			android:id="@+id/cbTraffic" />
	</LinearLayout>
	<Button
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:layout_alignParentBottom="true"
		android:layout_alignParentRight="true"
		android:layout_margin="10dp"
		android:id="@+id/btnGoTo"
		android:text="Go to" />
	<Button
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:id="@+id/btnMark"
		android:text="Mark position"
		android:layout_margin="10dp"
		android:layout_alignParentBottom="true"
		android:layout_alignParentLeft="true" />
</RelativeLayout>

Oczywiście aby wykorzystać powyższy kod należy pamiętać by zmienić wartość atrybutu android:apiKey w MapView, na swój własny klucz do API Google Maps (informacje na temat generowania klucza do API map Google zawarte są w poprzednim wpisie dotyczącym biblioteki).

Przyciski do przybliżenia/oddalenia widoku mapy

Aby wyświetlić przyciski odpowiedzialne za skalę przybliżenia mapy, na obiekcie kontrolki MapView wystarczy wywołać metodę setBuiltInZoomControls(boolean).

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initUiComponents();
		initListeners();

		mapController = mapView.getController();
		mapView.setBuiltInZoomControls(true);

		myOverlay = new MyOverlay();
		overlays = mapView.getOverlays();
		overlays.add(myOverlay);

		Drawable drawable = this.getResources().getDrawable(R.drawable.pin);
		itemizedOverlay = new MyItemizedOverlay(drawable, getApplicationContext());
	}

	private void initUiComponents() {
		cbSatellite = (CheckBox) findViewById(R.id.cbSatellite);
		cbStreet = (CheckBox) findViewById(R.id.cbStreet);
		cbTraffic = (CheckBox) findViewById(R.id.cbTraffic);
		btnGoTo = (Button) findViewById(R.id.btnGoTo);
		btnMark = (Button) findViewById(R.id.btnMark);
		mapView = (MapView) findViewById(R.id.mapview);
	}

Jeżeli argumentem będzie wartość true, w momencie aktywności MapView (przesuwanie, tapnięcie) zostaną wyświetlone przyciski odpowiedzialne za powiększenie.

Zmiana nakładki mapy

Komponent MapView pozwala nam na zdefiniowanie z jakiego typu widoku chcemy korzystać. Domyślnie wykorzystywana jest nakładka mapy. My natomiast dodatkowo możemy włączyć widok satelitarny, StreetView (dokładnie tryb mapy z zaznaczonymi miejscami, gdzie StreetView jest dostępne) oraz Traffic (zaznaczenie ruchu ulicznego).

Implementacja zmiany nakładki w naszej aplikacji wygląda tak:

	private OnCheckedChangeListener cbViewListener = new OnCheckedChangeListener() {
		public void onCheckedChanged(CompoundButton button, boolean state) {
			switch (button.getId()) {
			case R.id.cbSatellite:
				mapView.setSatellite(state);
				break;
			case R.id.cbStreet:
				mapView.setStreetView(state);
				break;
			case R.id.cbTraffic:
				mapView.setTraffic(state);
				break;
			default:
				break;
			}
		}
	};
	private void initListeners() {
		cbSatellite.setOnCheckedChangeListener(cbViewListener);
		cbStreet.setOnCheckedChangeListener(cbViewListener);
		cbTraffic.setOnCheckedChangeListener(cbViewListener);
		btnGoTo.setOnClickListener(btnGoToListener);
		btnMark.setOnClickListener(btnMarkListener);
	}

Po kliknięciu na odpowiedni checkbox powinna wyświetlić się konkretna nakładka na mapie. Jednak nie zawsze działa to tak jak powinno. Należy bowiem pamiętać o tym, że widoki StreetView oraz Traffic wzajemnie się wykluczają, przez co kiedy włączymy jeden z nich, drugi jest automatycznie wyłączany. Aby w jakiś sposób rozwiązać ten problem możemy skorzystać z metod isSatellite(), isTraffic() czy isStreetView() obiektu MapView, które zwracają true gdy korzystamy w konkretnego widoku.

Kontrolowanie widoku mapy (przybliżanie, przesuwanie)

Kolejną opcją naszej przykładowej aplikacji jest przesuwanie mapy do konkretnych współrzędnych geograficznych.
Kontrolą widoku map zajmuje się obiekt klasy MapController, który możemy pobrać z obiektu MapView dzięki metodzie getController():

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initUiComponents();
		initListeners();

		mapController = mapView.getController();
		mapView.setBuiltInZoomControls(true);

		myOverlay = new MyOverlay();
		overlays = mapView.getOverlays();
		overlays.add(myOverlay);

		Drawable drawable = this.getResources().getDrawable(R.drawable.pin);
		itemizedOverlay = new MyItemizedOverlay(drawable, getApplicationContext());
	}

Za pomocą kontrolera możemy skoczyć (metoda setCenter(GeoPoint)) lub przejść za pomocą płynnej animacji (metoda animateTo(GeoPoint)) do wskazanego przez nas miejsca (opisanego przez obiekt klasy GeoPoint).
Dodatkowo możemy sterować powiększeniem (metody zoom…() oraz setZoom(int)).
Jako ciekawostkę dodam jeszcze, że obiekt klasy MapView dostarcza nam metody, która zwraca maksymalny możliwy zoom dla wyświetlanego aktualnie miejsca (metoda getMaxZoomLevel()).

W naszej aplikacji przesunięcie i powiększenie zadanego punktu odbędzie się za pomocą przycisku GoTo, który uruchomi okno dialogowe, w którym następnie będzie można wpisać odpowiednie współrzędne.

	@Override
	protected Dialog onCreateDialog(int id) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
		View layout = inflater.inflate(R.layout.dialog, (ViewGroup) findViewById(R.id.layout_root));
		builder.setView(layout).setTitle("Coordinates").setCancelable(true);
		builder.setPositiveButton("Go", new Dialog.OnClickListener() {
			public void onClick(DialogInterface dialog, int id) {
				AlertDialog ad = (AlertDialog) dialog;
				EditText etLat = (EditText) ad.findViewById(R.id.etLat);
				EditText etLong = (EditText) ad.findViewById(R.id.etLong);
				latitude = Double.parseDouble(etLat.getText().toString()) * 1E6;
				longitude = Double.parseDouble(etLong.getText().toString()) * 1E6;
				GeoPoint point = new GeoPoint(latitude.intValue(), longitude.intValue());
				mapController.animateTo(point);
				mapController.setZoom(17);
				myOverlay.setLatitude(latitude);
				myOverlay.setLongitude(longitude);
			}
		});
		Dialog dialog = builder.create();
		return dialog;
	}

Jeszcze kod listenera przycisku wyświetlającego okno dialogowe:

	private OnClickListener btnGoToListener = new OnClickListener() {
		public void onClick(View arg0) {
			showDialog(DIALOG_COORDINATES);
		}
	};

I layout okna:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_height="fill_parent"
	android:layout_width="fill_parent"
	android:id="@+id/layout_root">
	<TextView
		android:id="@+id/TextView01"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Type coordinates here: "
		android:layout_margin="5dp" />
	<EditText
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:hint="Latitude"
		android:id="@+id/etLat"
		android:inputType="numberDecimal"
		android:text="50.0544530059" />
	<EditText
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:hint="Longitude"
		android:id="@+id/etLong"
		android:inputType="numberDecimal"
		android:text="19.93668526411" />
</LinearLayout>

Dodawanie własnych warstw do MapView

Jedną z ważniejszych możliwości, jakich dostarcza nam Androidowa biblioteka Google Maps jest tworzenie własnych warstw, które możemy nakładać na widok mapy wyświetlanej w MapView. To właśnie dzięki temu do naszej mapy możemy dodawać „pinezki”, napisy w „dymkach” i tym podobne.
W dzisiejszym wpisie przedstawię dwie klasy, które służą do tego typu zabiegów – Overlay oraz ItemizedOverlay (która dziedziczy po pierwszej i znacznie ułatwia niektóre czynności).

Dodawanie „dymku” z informacją do mapy

Kolejnym elementem, jaki dodamy do naszej aplikacji jest klasa pozwalająca na stworzenie warstwy z dymkiem, w którym wyświetlony zostanie krótki tekst. „Dymek” będzie składał się z 3 elementów – punktu, prostokąta z tekstem oraz kształtu łączącego te dwa elementy.

Do projektu dodajemy nową klasę dziedziczącą po Overlay. Oto kod źródłowy od jakiego zaczniemy:

public class ImHereTipOverlay extends Overlay {
    private static final int radius = 5;
    private static final int width = 65;
    private static final int height = 20;

    private Double latitude = 0.0;
    private Double longitude = 0.0;

    public void setLatitude(Double latitude) {
        this.latitude = latitude;
    }

    public void setLongitude(Double longitude) {
        this.longitude = longitude;
    }

    @Override
    public void draw(Canvas canvas, MapView mapView, boolean shadow) {
        if(shadow == false) {

        }
        super.draw(canvas, mapView, shadow);
    }
}

Zajmiemy się teraz rozbudowaniem metody draw(Canvas, Mapview, boolean). Argument typu Canvas jest czymś w rodzaju niewidzialnego tła, na którym rysujemy elementy, które później zostaną nałożone na mapę. Wartość ostatniego argumentu wskazuje na to czy rysujemy cień czy właściwy obiekt (w przykładzie zajmiemy się tylko drugim przypadkiem).

	@Override
	public void draw(Canvas canvas, MapView mapView, boolean shadow) {
		if (shadow == false) {
			GeoPoint geoPoint = new GeoPoint(latitude.intValue(), longitude.intValue());
			Point point = getPointFrom(mapView, geoPoint);
			Paint spotPaint = getSpotPaint();
			Paint tipPaint = getTipPaint();
			RectF oval = getSpotShape(point);
			RectF backRect = getTipShape(point);
			Path trianglePath = getSpotToTipShape(point);
			canvas.drawOval(oval, spotPaint);
			canvas.drawPath(trianglePath, tipPaint);
			canvas.drawRoundRect(backRect, 5, 5, tipPaint);
			canvas.drawText("Your point",
					point.x + 3 * radius,
					point.y	+ radius - height,
					spotPaint);
		}
		super.draw(canvas, mapView, shadow);
	}

Rysowanie zaczynami od przetłumaczenia wartości współrzędnych geograficznych na współrzędne wyświetlacza, co pozwoli nam na precyzyjne określenie punktu, od którego chcemy rozpocząć pracę:

	@Override
	public void draw(Canvas canvas, MapView mapView, boolean shadow) {
		if (shadow == false) {
			GeoPoint geoPoint = new GeoPoint(latitude.intValue(), longitude.intValue());
			Point point = getPointFrom(mapView, geoPoint);
			Paint spotPaint = getSpotPaint();
			Paint tipPaint = getTipPaint();
			RectF oval = getSpotShape(point);
			RectF backRect = getTipShape(point);
			Path trianglePath = getSpotToTipShape(point);
			canvas.drawOval(oval, spotPaint);
			canvas.drawPath(trianglePath, tipPaint);
			canvas.drawRoundRect(backRect, 5, 5, tipPaint);
			canvas.drawText("Your point",
					point.x + 3 * radius,
					point.y	+ radius - height,
					spotPaint);
		}
		super.draw(canvas, mapView, shadow);
	}

	private Point getPointFrom(MapView mapView, GeoPoint geoPoint) {
		Point point = new Point();
		Projection projection = mapView.getProjection();
		projection.toPixels(geoPoint, point);
		return point;
	}

Pomoże nam w tym obiekt Projection, który pobieramy przez wywołanie metody getProjection() na obiekcie MapView. Pozwala on na tłumaczenie GeoPoint <-> Point (metody toPixels(GeoPoint, Point) oraz fromPixels(int, int)).

Następnie tworzymy pędzle oraz figury, które narysujemy:

	@Override
	public void draw(Canvas canvas, MapView mapView, boolean shadow) {
		if (shadow == false) {
			GeoPoint geoPoint = new GeoPoint(latitude.intValue(), longitude.intValue());
			Point point = getPointFrom(mapView, geoPoint);
			Paint spotPaint = getSpotPaint();
			Paint tipPaint = getTipPaint();
			RectF oval = getSpotShape(point);
			RectF backRect = getTipShape(point);
			Path trianglePath = getSpotToTipShape(point);
			canvas.drawOval(oval, spotPaint);
			canvas.drawPath(trianglePath, tipPaint);
			canvas.drawRoundRect(backRect, 5, 5, tipPaint);
			canvas.drawText("Your point",
					point.x + 3 * radius,
					point.y	+ radius - height,
					spotPaint);
		}
		super.draw(canvas, mapView, shadow);
	}

	private Point getPointFrom(MapView mapView, GeoPoint geoPoint) {
		Point point = new Point();
		Projection projection = mapView.getProjection();
		projection.toPixels(geoPoint, point);
		return point;
	}

	private Paint getSpotPaint() {
		Paint spotPaint = new Paint();
		spotPaint.setARGB(250, 255, 255, 255);
		spotPaint.setAntiAlias(true);
		spotPaint.setFakeBoldText(true);
		return spotPaint;
	}

	private Paint getTipPaint() {
		Paint tipPaint = new Paint();
		tipPaint.setARGB(200, 50, 50, 50);
		tipPaint.setAntiAlias(true);
		tipPaint.setStyle(Paint.Style.FILL_AND_STROKE);
		return tipPaint;
	}

	private RectF getSpotShape(Point point) {
		RectF oval = new RectF(point.x - radius,
				point.y - radius,
				point.x	+ radius,
				point.y + radius);
		return oval;
	}

	private RectF getTipShape(Point point) {
		RectF backRect = new RectF(point.x + 2 * radius,
				point.y - 2	* radius - height,
				point.x + 2 * radius + width,
				point.y	- 2 * radius);
		return backRect;
	}

	private Path getSpotToTipShape(Point point) {
		Path trianglePath = new Path();
		trianglePath.setFillType(Path.FillType.EVEN_ODD);
		trianglePath.moveTo(point.x, point.y);
		trianglePath.lineTo(point.x + radius + 20, point.y - 2 * radius);
		trianglePath.lineTo(point.x + radius + 35, point.y - 2 * radius);
		trianglePath.lineTo(point.x, point.y);
		trianglePath.close();
		return trianglePath;
	}

Na końcu dodajemy nasze figury do obiektu Canvas:

	@Override
	public void draw(Canvas canvas, MapView mapView, boolean shadow) {
		if (shadow == false) {
			GeoPoint geoPoint = new GeoPoint(latitude.intValue(), longitude.intValue());
			Point point = getPointFrom(mapView, geoPoint);
			Paint spotPaint = getSpotPaint();
			Paint tipPaint = getTipPaint();
			RectF oval = getSpotShape(point);
			RectF backRect = getTipShape(point);
			Path trianglePath = getSpotToTipShape(point);
			canvas.drawOval(oval, spotPaint);
			canvas.drawPath(trianglePath, tipPaint);
			canvas.drawRoundRect(backRect, 5, 5, tipPaint);
			canvas.drawText("Your point",
					point.x + 3 * radius,
					point.y	+ radius - height,
					spotPaint);
		}
		super.draw(canvas, mapView, shadow);
	}

W tym momencie możemy zakończyć edycję klasy ImHereTipOverlay.

Aby skorzystać z niej w naszej aplikacji, w metodzie onCreate() głównej Aktywności, tworzymy jego instancję, którą następnie dodajemy do listy warstw widoku MapView:

		imHereTipOverlay = new ImHereTipOverlay();
		overlays = mapView.getOverlays();
		overlays.add(imHereTipOverlay);

Aby narysować nasz dymek w wybranym przez nas punkcie wystarczy ustawić współrzędne w naszym obiekcie:

	@Override
	protected Dialog onCreateDialog(int id) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
		View layout = inflater.inflate(R.layout.dialog, (ViewGroup) findViewById(R.id.layout_root));
		builder.setView(layout).setTitle("Coordinates").setCancelable(true);
		builder.setPositiveButton("Go", new Dialog.OnClickListener() {
			public void onClick(DialogInterface dialog, int id) {
				AlertDialog ad = (AlertDialog) dialog;
				EditText etLat = (EditText) ad.findViewById(R.id.etLat);
				EditText etLong = (EditText) ad.findViewById(R.id.etLong);
				latitude = Double.parseDouble(etLat.getText().toString()) * 1E6;
				longitude = Double.parseDouble(etLong.getText().toString()) * 1E6;
				GeoPoint point = new GeoPoint(latitude.intValue(), longitude.intValue());
				mapController.animateTo(point);
				mapController.setZoom(17);
				imHereTipOverlay.setLatitude(latitude);
				imHereTipOverlay.setLongitude(longitude);
			}
		});
		Dialog dialog = builder.create();
		return dialog;
	}

co w przypadku naszej aplikacji odbywa się za pomocą okna dialogowego, o którym pisałem powyżej.

Dodawanie własnych ikonek do mapy

Ostatnią rzeczą, o której chciałem napisać w dzisiejszym poście jest dodawanie własnych obrazków (np. pinezek) do mapy. Tym razem naszą klasę oprzemy o ItemizedOverlay, która sama w sobie pozwala na przechowywanie pewnej ilości warstw.

Do projektu dodajemy kolejną klasę:

public class MyPinOverlay extends ItemizedOverlay<OverlayItem> {
	private ArrayList<OverlayItem> overlays = new ArrayList<OverlayItem>();
	private Context context;

	public MyPinOverlay(Drawable defaultMarker, Context context) {
		super(boundCenterBottom(defaultMarker));
		this.context = context;
	}

	@Override
	protected OverlayItem createItem(int i) {
		return overlays.get(i);
	}

	@Override
	public int size() {
		return overlays.size();
	}
}

Pole overlays będzie przechowywało wszystkie warstwy naszego obiektu. W konstruktorze, przy wywołaniu super(…) wykorzystujemy metodę boundCenterBottom(Drawable). Sprawia ona, że podczas rysowania ikony, na wskazanych przez nas współrzędnych spoczywać będzie dokładnie środkowa część dolnej krawędzi obrazka.
Bliźniacza metoda pozwalająca na umieszczenie środkowego punktu obrazka nazywa się boundCenter(Drawable).

W naszej klasie przeciążymy jeszcze metodę onTap(int), która jest wywoływana w momencie kliknięcia na jedną z warstw naszego obiektu:

	@Override
	protected boolean onTap(int index) {
		OverlayItem item = overlays.get(index);
		GeoPoint point = item.getPoint();
		showLocationToast(point);
		return true;
	}

	private void showLocationToast(GeoPoint point) {
		Toast.makeText(context,
				"Lat: " + point.getLatitudeE6() / 1E6 +
				"\nLon: " + point.getLongitudeE6() / 1E6,
				Toast.LENGTH_LONG).show();
	}

Wyświetli ona okiekno Toast z informacją o współrzędnych na które wskazuje dodany przez nas obrazek.

Na końcu do klasy dopiszemy metodę dodającą kolejną warstwę do listy:

	public void addOverlay(OverlayItem overlay) {
		overlays.add(overlay);
		populate();
	}

Metoda populate() powinna być wywołana za każdym razem po dodaniu nowej warstwy. Sprawia ona, że wszystkie elementy znajdujące się aktualnie na liście są przygotowywane do narysowania.

Aby skorzystać z klasy MyPinOverlay w metodzie onCreate() głównej aktywności inicjalizujemy jej pole:

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initUiComponents();
		initListeners();

		mapController = mapView.getController();
		mapView.setBuiltInZoomControls(true);

		imHereTipOverlay = new ImHereTipOverlay();
		overlays = mapView.getOverlays();
		overlays.add(imHereTipOverlay);

		Drawable drawable = this.getResources().getDrawable(R.drawable.pin);
		myPinOverlay = new MyPinOverlay(drawable, getApplicationContext());
	}

Obiekt drawable tworzony jest na podstawie obrazka pin.png zamieszczonego w folderze /res/drawable/ naszego projektu.

Na końcu tworzymy jeszcze listener dla przycisku Mark:

	private OnClickListener btnMarkListener = new OnClickListener() {
		public void onClick(View arg0) {
			GeoPoint point = new GeoPoint(latitude.intValue(), longitude.intValue());
			OverlayItem item = new OverlayItem(point, "", "");
			myPinOverlay.addOverlay(item);
			overlays.add(myPinOverlay);
			mapView.invalidate();
		}
	};

Pozwala on na oznaczenie „pinezką” punktu, na który wskazaliśmy chwilę wcześniej przyciskiem GoTo.

Kompletny kod źródłowy

Cały projekt dostępny jest na naszym Githubie – HelloGoogleMaps.

MainActivity.java

package pl.froger.hello.googlemaps;

import java.util.List;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayItem;

public class MainActivity extends MapActivity {
	private static final int DIALOG_COORDINATES = 1;

	private CheckBox cbSatellite;
	private CheckBox cbStreet;
	private CheckBox cbTraffic;
	private Button btnGoTo;
	private Button btnMark;
	private MapView mapView;

	private MapController mapController;
	private ImHereTipOverlay imHereTipOverlay;
	private MyPinOverlay myPinOverlay;
	private List<Overlay> overlays;
	private Double latitude = 0.0;
	private Double longitude = 0.0;

	private OnCheckedChangeListener cbViewListener = new OnCheckedChangeListener() {
		public void onCheckedChanged(CompoundButton button, boolean state) {
			switch (button.getId()) {
			case R.id.cbSatellite:
				mapView.setSatellite(state);
				break;
			case R.id.cbStreet:
				mapView.setStreetView(state);
				break;
			case R.id.cbTraffic:
				mapView.setTraffic(state);
				break;
			default:
				break;
			}
		}
	};

	private OnClickListener btnGoToListener = new OnClickListener() {
		public void onClick(View arg0) {
			showDialog(DIALOG_COORDINATES);
		}
	};

	private OnClickListener btnMarkListener = new OnClickListener() {
		public void onClick(View arg0) {
			GeoPoint point = new GeoPoint(latitude.intValue(), longitude.intValue());
			OverlayItem item = new OverlayItem(point, "", "");
			myPinOverlay.addOverlay(item);
			overlays.add(myPinOverlay);
			mapView.invalidate();
		}
	};

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initUiComponents();
		initListeners();

		mapController = mapView.getController();
		mapView.setBuiltInZoomControls(true);

		imHereTipOverlay = new ImHereTipOverlay();
		overlays = mapView.getOverlays();
		overlays.add(imHereTipOverlay);

		Drawable drawable = this.getResources().getDrawable(R.drawable.pin);
		myPinOverlay = new MyPinOverlay(drawable, getApplicationContext());
	}

	private void initUiComponents() {
		cbSatellite = (CheckBox) findViewById(R.id.cbSatellite);
		cbStreet = (CheckBox) findViewById(R.id.cbStreet);
		cbTraffic = (CheckBox) findViewById(R.id.cbTraffic);
		btnGoTo = (Button) findViewById(R.id.btnGoTo);
		btnMark = (Button) findViewById(R.id.btnMark);
		mapView = (MapView) findViewById(R.id.mapview);
	}

	private void initListeners() {
		cbSatellite.setOnCheckedChangeListener(cbViewListener);
		cbStreet.setOnCheckedChangeListener(cbViewListener);
		cbTraffic.setOnCheckedChangeListener(cbViewListener);
		btnGoTo.setOnClickListener(btnGoToListener);
		btnMark.setOnClickListener(btnMarkListener);
	}

	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}

	@Override
	protected Dialog onCreateDialog(int id) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
		View layout = inflater.inflate(R.layout.dialog, (ViewGroup) findViewById(R.id.layout_root));
		builder.setView(layout).setTitle("Coordinates").setCancelable(true);
		builder.setPositiveButton("Go", new Dialog.OnClickListener() {
			public void onClick(DialogInterface dialog, int id) {
				AlertDialog ad = (AlertDialog) dialog;
				EditText etLat = (EditText) ad.findViewById(R.id.etLat);
				EditText etLong = (EditText) ad.findViewById(R.id.etLong);
				latitude = Double.parseDouble(etLat.getText().toString()) * 1E6;
				longitude = Double.parseDouble(etLong.getText().toString()) * 1E6;
				GeoPoint point = new GeoPoint(latitude.intValue(), longitude.intValue());
				mapController.animateTo(point);
				mapController.setZoom(17);
				imHereTipOverlay.setLatitude(latitude);
				imHereTipOverlay.setLongitude(longitude);
			}
		});
		Dialog dialog = builder.create();
		return dialog;
	}
}

ImHereTipOverlay.java

package pl.froger.hello.googlemaps;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;

public class ImHereTipOverlay extends Overlay {
    private static final int radius = 5;
    private static final int width = 65;
    private static final int height = 20;

    private Double latitude = 0.0;
    private Double longitude = 0.0;

    public void setLatitude(Double latitude) {
        this.latitude = latitude;
    }

    public void setLongitude(Double longitude) {
        this.longitude = longitude;
    }

	@Override
	public void draw(Canvas canvas, MapView mapView, boolean shadow) {
		if (shadow == false) {
			GeoPoint geoPoint = new GeoPoint(latitude.intValue(), longitude.intValue());
			Point point = getPointFrom(mapView, geoPoint);
			Paint spotPaint = getSpotPaint();
			Paint tipPaint = getTipPaint();
			RectF oval = getSpotShape(point);
			RectF backRect = getTipShape(point);
			Path trianglePath = getSpotToTipShape(point);
			canvas.drawOval(oval, spotPaint);
			canvas.drawPath(trianglePath, tipPaint);
			canvas.drawRoundRect(backRect, 5, 5, tipPaint);
			canvas.drawText("Your point",
					point.x + 3 * radius,
					point.y	+ radius - height,
					spotPaint);
		}
		super.draw(canvas, mapView, shadow);
	}

	private Point getPointFrom(MapView mapView, GeoPoint geoPoint) {
		Point point = new Point();
		Projection projection = mapView.getProjection();
		projection.toPixels(geoPoint, point);
		return point;
	}

	private Paint getSpotPaint() {
		Paint spotPaint = new Paint();
		spotPaint.setARGB(250, 255, 255, 255);
		spotPaint.setAntiAlias(true);
		spotPaint.setFakeBoldText(true);
		return spotPaint;
	}

	private Paint getTipPaint() {
		Paint tipPaint = new Paint();
		tipPaint.setARGB(200, 50, 50, 50);
		tipPaint.setAntiAlias(true);
		tipPaint.setStyle(Paint.Style.FILL_AND_STROKE);
		return tipPaint;
	}

	private RectF getSpotShape(Point point) {
		RectF oval = new RectF(point.x - radius,
				point.y - radius,
				point.x	+ radius,
				point.y + radius);
		return oval;
	}

	private RectF getTipShape(Point point) {
		RectF backRect = new RectF(point.x + 2 * radius,
				point.y - 2	* radius - height,
				point.x + 2 * radius + width,
				point.y	- 2 * radius);
		return backRect;
	}

	private Path getSpotToTipShape(Point point) {
		Path trianglePath = new Path();
		trianglePath.setFillType(Path.FillType.EVEN_ODD);
		trianglePath.moveTo(point.x, point.y);
		trianglePath.lineTo(point.x + radius + 20, point.y - 2 * radius);
		trianglePath.lineTo(point.x + radius + 35, point.y - 2 * radius);
		trianglePath.lineTo(point.x, point.y);
		trianglePath.close();
		return trianglePath;
	}
}

MyPinOverlay.java

package pl.froger.hello.googlemaps;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.widget.Toast;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.OverlayItem;

public class MyPinOverlay extends ItemizedOverlay<OverlayItem> {
	private ArrayList<OverlayItem> overlays = new ArrayList<OverlayItem>();
	private Context context;

	public MyPinOverlay(Drawable defaultMarker, Context context) {
		super(boundCenterBottom(defaultMarker));
		this.context = context;
	}

	@Override
	protected OverlayItem createItem(int i) {
		return overlays.get(i);
	}

	@Override
	public int size() {
		return overlays.size();
	}

	@Override
	protected boolean onTap(int index) {
		OverlayItem item = overlays.get(index);
		GeoPoint point = item.getPoint();
		showLocationToast(point);
		return true;
	}

	private void showLocationToast(GeoPoint point) {
		Toast.makeText(context,
				"Lat: " + point.getLatitudeE6() / 1E6 +
				"\nLon: " + point.getLongitudeE6() / 1E6,
				Toast.LENGTH_LONG).show();
	}

	public void addOverlay(OverlayItem overlay) {
		overlays.add(overlay);
		populate();
	}
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<com.google.android.maps.MapView
		xmlns:android="http://schemas.android.com/apk/res/android"
		android:id="@+id/mapview"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:enabled="true"
		android:clickable="true"
		android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
	<LinearLayout
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:id="@+id/LinearLayout01"
		android:orientation="vertical"
		android:gravity="right"
		android:layout_alignParentRight="true"
		android:layout_margin="10dp"
		android:padding="5dp"
		android:background="#44000000">
		<CheckBox
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="Satellite"
			android:layout_gravity="left"
			android:id="@+id/cbSatellite" />
		<CheckBox
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="Street"
			android:layout_gravity="left"
			android:id="@+id/cbStreet" />
		<CheckBox
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="Traffic"
			android:layout_gravity="left"
			android:id="@+id/cbTraffic" />
	</LinearLayout>
	<Button
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:layout_alignParentBottom="true"
		android:layout_alignParentRight="true"
		android:layout_margin="10dp"
		android:id="@+id/btnGoTo"
		android:text="Go to" />
	<Button
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:id="@+id/btnMark"
		android:text="Mark position"
		android:layout_margin="10dp"
		android:layout_alignParentBottom="true"
		android:layout_alignParentLeft="true" />
</RelativeLayout>

dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_height="fill_parent"
	android:layout_width="fill_parent"
	android:id="@+id/layout_root">
	<TextView
		android:id="@+id/TextView01"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Type coordinates here: "
		android:layout_margin="5dp" />
	<EditText
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:hint="Latitude"
		android:id="@+id/etLat"
		android:inputType="numberDecimal"
		android:text="50.0544530059" />
	<EditText
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:hint="Longitude"
		android:id="@+id/etLong"
		android:inputType="numberDecimal"
		android:text="19.93668526411" />
</LinearLayout>

Komentarze (2) Subskrybuj

 

  1. Topor pisze:

    Coś się fotki nie wczytują.

  2. Dzięki. Faktycznie troszkę się posypało przy przenoszeniu strony pod nowy adres. Aczkolwiek wszystko jest już ok. :)

Prześlij komentarz

Zaloguj się lub skorzystaj z profilu:

[rpxlogin redirect="http://www.android4devs.pl" prompt="" style="large"]

Możesz również zostawić komentarz bez rejestracji, korzystając z poniższego formularza:

Musisz być zalogowany aby móc pisać komentarze.