I'm trying to create an Activity with MapView and ListView inside it. I use C# and MonoDroid.
Layout looks like this
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<app.MD.UI.PlacesMapView
android:id="@+id/mapView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:enabled="true"
android:clickable="true"
android:layout_weight="1"
android:apiKey="key" />
<ListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:id="@+id/resultsTable" />
</LinearLayout>
My custom MapView class implements long press handler
private void HandleLongPress(MotionEvent e)
{
if (e.Action == MotionEventActions.Down) {
// Finger has touched screen.
longpressTimer = new Timer(LONGPRESS_THRESHOLD);
longpressTimer.AutoReset = false;
longpressTimer.Elapsed += delegate(object sender, ElapsedEventArgs e_elapsed) {
GeoPoint longpressLocation = Projection.FromPixels((int)e.GetX(), (int)e.GetY());
if (OnLongPress != null) {
OnMapViewLongPressEventArgs args = new OnMapViewLongPressEventArgs();
args.location = longpressLocation;
OnLongPress(this, args);
}
};
longpressTimer.Start();
lastMapCenter = MapCenter;
}
if (e.Action == MotionEventActions.Move) {
if (!MapCenter.Equals(lastMapCenter)) {
longpressTimer.Stop();
}
lastMapCenter = MapCenter;
}
if (e.Action == MotionEventActions.Up) {
// User has removed finger from map.
longpressTimer.Stop();
}
if (e.PointerCount > 1) {
// This is a multitouch event, probably zooming.
longpressTimer.Stop();
}
}
Activity method, which adds marker to the MapView
protected void AddPinToMap(GeoPoint location, string title, bool scrollTo, int zoom)
{
var pin = Resources.GetDrawable(Resource.Drawable.maps_pin);
PinOverlay pinOverlay = new PinOverlay(pin, this);
OverlayItem item = new OverlayItem(location, title, "");
pinOverlay.AddOverlay(item);
mapView.Overlays.Clear();
mapView.Overlays.Add(pinOverlay);
if (scrollTo) {
mapView.Controller.AnimateTo(location);
}
mapView.Controller.SetZoom(zoom);
}
And this is my PinOverlay class
class PinOverlay : ItemizedOverlay
{
private List<OverlayItem> _items = new List<OverlayItem>();
private Context _context;
public PinOverlay(Drawable pin, Context context) : base(pin)
{
_context = context;
BoundCenterBottom(pin);
Populate();
}
protected override Java.Lang.Object CreateItem(int i)
{
return _items[i];
}
public override int Size()
{
return _items.Count();
}
public void AddOverlay(OverlayItem overlay) {
_items.Add(overlay);
Populate();
}
}
My problem is that when I touch MapView my marker shows lower than the point I've touched. I've thought that it may be caused my the fact that my MapView doesn't take full screen. When I deleted ListView so that MapView could occupy whole screen, I was able to put a marker in exact point I've touched on the screen.
Any suggestions or examples about how to handle touch events if MapView takes only part of the screen?
UPD Found some kind of a workaround, still need to test it with different screen sizes
Convert touch coordinates
public static Point ConvertCoordinates(Context context, MapView mapView, double x, double y)
{
var wm = context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
var display = wm.DefaultDisplay;
double diffY = (Convert.ToDouble(display.Height) / 2 - Convert.ToDouble(mapView.Height) / 2) / 2 - 20;
double diffX = Convert.ToDouble(display.Width) / 2 - Convert.ToDouble(mapView.Width) / 2;
Point size = new Point();
size.X = (int)(x - diffX);
size.Y = (int)(y - diffY);
return size;
}
Use it like this
var point = MapHelper.ConvertCoordinates(this.Context, this, e.GetX(), e.GetY());
GeoPoint longpressLocation = Projection.FromPixels(point.X, point.Y);
Finally I've found the reason of the problem.
This part was wrong, because Projection was accessed inside the Elapsed delegate. But by that time Projection has already changed its state, which resulted in wrong coordinates.
if (e.Action == MotionEventActions.Down) {
// Finger has touched screen.
longpressTimer = new Timer(LONGPRESS_THRESHOLD);
longpressTimer.AutoReset = false;
longpressTimer.Elapsed += delegate(object sender, ElapsedEventArgs e_elapsed) {
GeoPoint longpressLocation = Projection.FromPixels((int)e.GetX(), (int)e.GetY());
if (OnLongPress != null) {
OnMapViewLongPressEventArgs args = new OnMapViewLongPressEventArgs();
args.location = longpressLocation;
OnLongPress(this, args);
}
};
longpressTimer.Start();
lastMapCenter = MapCenter;
}
So the solution is to get GeoPoint at the very beginning of OnTouchEvent.
if (e.Action == MotionEventActions.Down) {
capturedProjection = Projection.FromPixels((int)e.GetX(), (int)e.GetY());
// Finger has touched screen.
longpressTimer = new Timer(LONGPRESS_THRESHOLD);
longpressTimer.AutoReset = false;
longpressTimer.Elapsed += (object sender, ElapsedEventArgs e_timer) => {
if (OnLongPress != null) {
OnMapViewLongPressEventArgs args = new OnMapViewLongPressEventArgs();
args.location = capturedProjection;
OnLongPress(this, args);
}
};
longpressTimer.Start();
lastMapCenter = MapCenter;
}