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)

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

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 5378 on Developer Community or GitHub if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
ASSIGNED

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;
		}
 
 
	}

}