Search code examples
xamarinxamarin.androidimageviewxamarin.formscustom-renderer

How can I interact with a xamarin forms image in Android?


I have an image in Xamarin Forms. I want to use the native android features to interact with this image. For example, when the image is tapped, I want to know the x,y coordinates of where the image was tapped. I can use Android ImageView but I'm not sure how to cast the Xamarin Forms image to Android ImageView

[assembly: ExportRenderer(typeof(Image), typeof(FloorplanImageRenderer))]
namespace EmployeeApp.Droid.Platform
{

  public class FloorplanImageRenderer : ImageRenderer
  {
        protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
        {
            if (Control == null)
            {

                var imageView = (ImageView)e.NewElement; // This is not right

            }

            base.OnElementChanged(e);
        }
   }
}

Solution

  • But the Control is null....

    No, it shouldn't be null. Back to your question, I think first of all, you will need to attach a touch event to the image control in PCL and create a property to hold the coordinate when image get touched. And I think here in your code:

    [assembly: ExportRenderer(typeof(Image), typeof(FloorplanImageRenderer))]
    

    I think the Image here should be your custom image control which inherits from Image in PCL.

    Create a interface for touch event:

    public interface IFloorplanImageController
    {
        void SendTouched();
    }
    

    Create a custom control for image:

     public class FloorplanImage : Image, IFloorplanImageController
     {
         public event EventHandler Touched;
    
         public void SendTouched()
         {
             Touched?.Invoke(this, EventArgs.Empty);
         }
    
         public Tuple<float, float> TouchedCoordinate
         {
             get { return (Tuple<float, float>)GetValue(TouchedCoordinateProperty); }
             set { SetValue(TouchedCoordinateProperty, value); }
         }
    
         public static readonly BindableProperty TouchedCoordinateProperty =
             BindableProperty.Create(
                 propertyName: "TouchedCoordinate",
                 returnType: typeof(Tuple<float, float>),
                 declaringType: typeof(FloorplanImage),
                 defaultValue: new Tuple<float, float>(0, 0),
                 propertyChanged: OnPropertyChanged);
    
         public static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
         {
         }
     }
    

    Implement the custom renderer:

    [assembly: ExportRenderer(typeof(FloorplanImage), typeof(FloorplanImageRenderer))]
    
    namespace EmployeeApp.Droid.Platform
    {
        public class FloorplanImageRenderer : ImageRenderer
        {
            protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
            {
                base.OnElementChanged(e);
    
                if (e.NewElement != null)
                {
                    if (Control != null)
                    {
                        Control.Clickable = true;
                        Control.SetOnTouchListener(ImageTouchListener.Instance.Value);
                        Control.SetTag(Control.Id, new JavaObjectWrapper<FloorplanImage> { Obj = Element as FloorplanImage });
                    }
                }
            }
    
            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    if (Control != null)
                    {
                        Control.SetOnTouchListener(null);
                    }
                }
    
                base.Dispose(disposing);
            }
    
            private class ImageTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
            {
                public static readonly Lazy<ImageTouchListener> Instance = new Lazy<ImageTouchListener>(
                    () => new ImageTouchListener());
    
                public bool OnTouch(Android.Views.View v, MotionEvent e)
                {
                    var obj = v.GetTag(v.Id) as JavaObjectWrapper<FloorplanImage>;
                    var element = obj.Obj;
                    var controller = element as IFloorplanImageController;
                    if (e.Action == Android.Views.MotionEventActions.Down)
                    {
                        var x = e.GetX();
                        var y = e.GetY();
                        element.TouchedCoordinate = new Tuple<float, float>(x, y);
                        controller?.SendTouched();
                    }
                    else if (e.Action == Android.Views.MotionEventActions.Up)
                    {
                    }
                    return false;
                }
            }
        }
    
        public class JavaObjectWrapper<T> : Java.Lang.Object
        {
            public T Obj { get; set; }
        }
    }
    

    Use this control like this:

    <local:FloorplanImage HeightRequest="300" x:Name="image" WidthRequest="300"
                          Aspect="AspectFit"  Touched="image_Touched" />
    

    code behind:

    private void image_Touched(object sender, EventArgs e)
    {
        var cor = image.TouchedCoordinate;
    }