Bug 5378 - Customized ItemizedOverlay requires BoundCenter() call in ctor
Summary: Customized ItemizedOverlay requires BoundCenter() call in ctor
Status: ASSIGNED
Alias: None
Product: Android
Classification: Xamarin
Component: General (show other bugs)
Version: 4.0
Hardware: Macintosh Mac OS
: Lowest normal
Target Milestone: ---
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2012-05-28 19:35 UTC by Ian
Modified: 2016-09-02 19:57 UTC (History)
2 users (show)

See Also:
Tags: XATriaged
Is this bug a regression?: ---
Last known good build:


Attachments

Description Ian 2012-05-28 19:35:33 UTC
I am converting a Java library for balloon popups on maps. See: https://github.com/jgilfelt/android-mapviewballoons

The Java version doesn't require a BoundCenter or related call in the ItemizedOverlay ctor. The only way to get the pin to appear on the c# version was to call BoundCenter or related.

Not sure if that's a bug or not, but sure made the port from Java a nightmare.



public class BalloonItemizedOverlay<T> : ItemizedOverlay where T: OverlayItem
	{

		public BalloonItemizedOverlay (Drawable defaultMarker, MapView mapView): base(defaultMarker)
		{
			this.mapView = mapView;
			viewOffset = 0;
			mc = mapView.Controller;
		        
                        //MUST be here for map pin to appear
                        BoundCenter (defaultMarker);
			Populate ();
		}
}
Comment 1 Jonathan Pryor 2013-05-23 13:59:09 UTC
Can we get a copy of your port?

Thanks,
 - Jon
Comment 2 Ian 2013-05-23 15:14:52 UTC
Hi Jon,

Here you go. You can use it for any reason. Give it away etc. 

It could use some loving to clean up for production code quality, but it works.

Enjoy,
Ian



/*
 *  PORT of : https://github.com/jgilfelt/android-mapviewballoons
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.GoogleMaps;
using Android.Graphics.Drawables;
using Android.Graphics;
using Android.Util;

namespace Police.CrimeStoppers
{
	/// <summary>
	/// Balloon itemized overlay to support a MKAnnotion-like ballon over map pins
	/// </summary>
	public class BalloonItemizedOverlay<T> : ItemizedOverlay where T: OverlayItem
	{

		private static  long BALLOON_INFLATION_TIME = 300;
		private static Handler handler = new Handler ();
		private MapView mapView;
		private BalloonOverlayView<T> balloonView;
		private View clickRegion;
		private View closeRegion;
		private int viewOffset;
		MapController mc;
		private T currentFocusedItem;
		private int currentFocusedIndex;
		private bool showClose = true;
		private bool showDisclosure = false;
		private bool snapToCenter = true;
		private static bool isInflating = false;
		private Action<int,T> onBallonTapped = null;
		private List<T> m_overlays = new List<T> ();

		/// <summary>
		/// Create a new BalloonItemizedOverlay
		/// </summary>
		/// <param name='defaultMarker'>
		/// A bounded Drawable to be drawn on the map for each item in the overlay.
		/// </param>
		/// <param name='mapView'>
		/// The view upon which the overlay items are to be drawn.
		/// </param>
		public BalloonItemizedOverlay (Drawable defaultMarker, MapView mapView): base(defaultMarker)
		{
			this.mapView = mapView;
			viewOffset = 0;
			mc = mapView.Controller;
			BoundCenter (defaultMarker);
			Populate ();
		}


		/// <summary>
		/// Set the horizontal distance between the marker and the bottom of the information
		/// balloon. The default is 0 which works well for center bounded markers. If your
		/// marker is center-bottom bounded, call this before adding overlay items to ensure
		/// the balloon hovers exactly above the marker. 
		/// </summary>
		/// <param name='pixels'>
		/// The padding between the center point and the bottom of the information balloon.
		/// </param>
		public void SetBalloonBottomOffset (int pixelsPadding)
		{
			viewOffset = pixelsPadding;
		}

		public int GetBalloonBottomOffset ()
		{
			return viewOffset;
		}

		public void AddOverlay (T Overlay)
		{
			m_overlays.Add (Overlay);

			Populate ();
		}

		protected override Java.Lang.Object CreateItem (int i)
		{
			var item = m_overlays [i];
			return item;
		}

		public override int Size ()
		{
			return m_overlays.Count;
		}

		/// <summary>
		/// Sets the on balloon tap Action to be called when tapped
		/// </summary>
		/// <param name='OnBallonTapped'>
		/// The Action to call
		/// </param>
		public void SetOnBalloonTap (Action<int,T> OnBallonTapped)
		{
			onBallonTapped = OnBallonTapped; 
		}




		/// <summary>
		/// Override this method to perform actions upon an item being tapped before  its balloon is displayed.
		/// </summary>
		/// <param name='index'>
		/// The index of the item tapped.
		/// </param>
		protected virtual void OnBalloonOpen (int index)
		{
		}

		static void FinishBalloonInflation ()
		{
			isInflating = false;
		}

		protected override bool OnTap (int index)
		{
			handler.RemoveCallbacks (FinishBalloonInflation);
			isInflating = true;
			handler.PostDelayed (FinishBalloonInflation, BALLOON_INFLATION_TIME);
		
			currentFocusedIndex = index;
			currentFocusedItem = (T)CreateItem (index);
			LastFocusedIndex = index;

			OnBalloonOpen (index);
			CreateAndDisplayBalloonOverlay ();
		
			if (snapToCenter)
			{
				AnimateTo (index, currentFocusedItem.Point);
			}
		
			return true;
		}

		/// <summary>
		/// Animates to the given center point. Override to customize how the MapView is animated to the given center point
		/// </summary>
		/// <param name='index'>
		/// The index of the item to center
		/// </param>
		/// <param name='center'>
		/// The center point of the item
		/// </param>
		protected void AnimateTo (int index, GeoPoint center)
		{
			mc.AnimateTo (center);
		}

		/// <summary>
		/// Creates the balloon view. Override to create a sub-classed view that can populate additional sub-views.
		/// </summary>
		/// <returns>
		/// The balloon overlay view.
		/// </returns>
		protected BalloonOverlayView<T> CreateBalloonOverlayView ()
		{
			return new BalloonOverlayView<T> (
				GetMapView ().Context,
				GetBalloonBottomOffset ()
			);
		}

		/// <summary>
		/// Expose map view to subclasses. Helps with creation of balloon views.
		/// </summary>
		/// <returns>
		/// The map view.
		/// </returns>
		protected MapView GetMapView ()
		{
			return mapView;
		}

		/// <summary>
		/// Sets the visibility of this overlay's balloon view to GONE and unfocus the item. 
		/// </summary>
		public void HideBalloon ()
		{
			if (balloonView != null)
			{
				balloonView.Visibility = ViewStates.Gone;
			}
			currentFocusedItem = null;
		}

		/// <summary>
		/// Hides the balloon view for any other BalloonItemizedOverlay instances that might be present on the MapView.
		/// </summary>
		/// <param name='overlays'>
		/// list of overlays (including this) on the MapView.
		/// </param>
		private void HideOtherBalloons (List<Overlay> overlays)
		{
		
			foreach (Overlay overlay in overlays)
			{
				if (overlay is BalloonItemizedOverlay<T> && overlay != this)
				{
					((BalloonItemizedOverlay<T>)overlay).HideBalloon ();
				}
			}
		
		}

		public void HideAllBalloons ()
		{
			if (!isInflating)
			{
				List<Overlay> mapOverlays = mapView.Overlays.ToList ();
				if (mapOverlays.Count > 1)
				{
					HideOtherBalloons (mapOverlays);
				}
				HideBalloon ();
			}
		}

		void onTouch (object sender, View.TouchEventArgs e)
		{
			float startX = 0;
			float startY = 0;

			View l = null;
			Drawable d = null;

			if (sender is View)
			{
				var parent = ((View)(sender)).Parent as View;
				if (parent != null)
					l = parent.FindViewById<View> (Resource.Id.balloon_main_layout);
				if (l != null)
					d = l.Background;
			}

			if (e.Event.Action == MotionEventActions.Down)
			{
				int[] states = {Android.Resource.Attribute.StatePressed};
				if (d != null && d.SetState (states))
				{
					d.InvalidateSelf ();
				}
				startX = e.Event.GetX ();
				startY = e.Event.GetY ();
				e.Handled = true;
			} else if (e.Event.Action == MotionEventActions.Up)
			{
					 
				if (d != null && d.SetState (new int[0]))
				{
					d.InvalidateSelf ();
				}
				if (Math.Abs (startX - e.Event.GetX ()) < 40 && 
					Math.Abs (startY - e.Event.GetY ()) < 40)
				{
					// call overridden method
					if (onBallonTapped != null)
						onBallonTapped (currentFocusedIndex, currentFocusedItem);
				}
				e.Handled = true;
			} else
			{
				e.Handled = false;
			}
				
		}
	
		public override Java.Lang.Object Focus
		{
			get {
				return currentFocusedItem;
			}
			set {
				currentFocusedIndex = LastFocusedIndex;
				currentFocusedItem = (T)value;
				if (currentFocusedItem == null)
				{
					HideBalloon ();
				} else
				{
					CreateAndDisplayBalloonOverlay ();
				}
			}
		}

		/// <summary>
		/// Creates and displays the balloon overlay by recycling the current  balloon or by inflating it from xml. 
		/// </summary>
		/// <returns>
		/// true if the balloon was recycled false otherwise 
		/// </returns>
		private bool CreateAndDisplayBalloonOverlay ()
		{
			bool isRecycled;
			if (balloonView == null)
			{
				balloonView = CreateBalloonOverlayView ();
				clickRegion = balloonView.FindViewById<View> (Resource.Id.balloon_inner_layout);
				clickRegion.Touch += onTouch;
				closeRegion = balloonView.FindViewById<View> (Resource.Id.balloon_close);
				if (closeRegion != null)
				{
					if (!showClose)
					{
						closeRegion.Visibility = ViewStates.Gone;
					} else
					{

						closeRegion.Click += delegate {
							HideBalloon ();
						}; 
					}
				}
				if (showDisclosure && !showClose)
				{
					View v = balloonView.FindViewById<View> (Resource.Id.balloon_disclosure);
					if (v != null)
					{
						v.Visibility = ViewStates.Visible;
					}
				}
				isRecycled = false;
			} else
			{
				isRecycled = true;
			}
	
			balloonView.Visibility = ViewStates.Gone;
		
			List<Overlay> mapOverlays = mapView.Overlays.ToList ();
			if (mapOverlays.Count > 1)
			{
				HideOtherBalloons (mapOverlays);
			}
		
			if (currentFocusedItem != null)
				balloonView.SetData (currentFocusedItem);
		
			GeoPoint point = currentFocusedItem.Point;

			//http://stackoverflow.com/questions/10778989/android-java-to-mono-c-sharp-mapview-layoutparams
			MapView.LayoutParams layoutParams = new MapView.LayoutParams (
				MapView.LayoutParams.WrapContent,
				MapView.LayoutParams.WrapContent,
				point,
				MapView.LayoutParams.BottomCenter
			);
			layoutParams.Mode = MapView.LayoutParams.ModeMap;
		
			balloonView.Visibility = ViewStates.Visible;
		
			if (isRecycled)
			{
				balloonView.LayoutParameters = layoutParams;
			} else
			{
				mapView.AddView (balloonView, layoutParams);
			}
		
			return isRecycled;
		}
 
		public void SetShowClose (bool showClose)
		{
			this.showClose = showClose;
		}

		public void SetShowDisclosure (bool showDisclosure)
		{
			this.showDisclosure = showDisclosure;
		}

		public void SetSnapToCenter (bool snapToCenter)
		{
			this.snapToCenter = snapToCenter;
		}

		public static bool IsInflating ()
		{
			return isInflating;
		}
 
 
	}

}

Note You need to log in before you can comment on or make changes to this bug.