Recently I came up with a great solution. As you know, Xamarin forms have no event or gesture recognizer for longpress. So today I came up with a simple solution using Rendering.
Let's create a shared custom control in our shared code (form project), name the class CustomImage, and Inherit it with Xamarin.Forms.Image
CustomImage.cs
- public class CustomImage : Image
- {
-
- public static readonly BindableProperty LongpressCommandProperty =
- BindableProperty.Create(nameof(LongpressCommand), typeof(ICommand), typeof(CustomImage), null);
-
- public ICommand LongpressCommand
- {
- get { return (ICommand)GetValue(LongpressCommandProperty); }
- set { SetValue(LongpressCommandProperty, value); }
- }
-
- public static readonly BindableProperty CommandProperty =
- BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(CustomImage), null);
-
- public ICommand Command
- {
- get { return (ICommand)GetValue(CommandProperty); }
- set { SetValue(CommandProperty, value); }
- }
-
- public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(CustomImage), (object)null);
-
- public object CommandParameter
- {
- get { return GetValue(CommandParameterProperty); }
- set { SetValue(CommandParameterProperty, value); }
- }
-
-
-
-
-
-
-
- public event EventHandler LongPressed
- {
- add { LongPressedHandler += value; }
- remove { LongPressedHandler -= value; }
- }
- public EventHandler LongPressedHandler;
-
-
-
-
-
- public event EventHandler Tapped
- {
- add { TappedHandler += value; }
- remove { TappedHandler -= value; }
- }
- public EventHandler TappedHandler;
-
-
-
-
- }
Now we have 3 bindable properties – the LongpressCommand
that we want to bind when the longpress detects the command when Tap is detected and
the CommandParameter
passes into the Command
.
We can use these in our native Renderer implementations to invoke when the press is detected. Let’s create our Android implementation.
In Android project,
CustomImageRenderer.cs
- [assembly: ExportRenderer(typeof(CustomImage), typeof(CustomImageRenderer))]
- namespace LongpressSample.Droid.CustomRenderer
- {
-
- public class CustomImageRenderer : ImageRenderer
- {
- private CustomImage _view;
-
- public CustomImageRenderer(Context context) : base(context) { }
-
- protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
- {
- base.OnElementChanged(e);
-
-
- if (e.NewElement != null)
- {
- _view = e.NewElement as CustomImage;
- }
-
- Control.SoundEffectsEnabled = true;
-
- Control.LongClickable = true;
- Control.LongClick += (s, ea) =>
- {
- if (_view != null)
- {
- _view.LongPressedHandler?.Invoke(_view, ea);
- var command = _view.LongpressCommand;
- command?.Execute(_view);
-
- }
- };
-
- Control.Clickable = true;
- Control.Click += (s, ea) =>
- {
- if (_view != null)
- {
- _view.TappedHandler?.Invoke(_view, ea);
- var command = _view.Command;
- command?.Execute(_view);
- }
- };
- }
- }
Now in IOS,
CustomImageRenderer.cs
- [assembly: ExportRenderer(typeof(CustomImage), typeof(CutomImageRenderer))]
- namespace LongpressSample.iOS.CustomRenderer
- {
- public class CutomImageRenderer : ImageRenderer
- {
- private CustomImage _view;
- private readonly UILongPressGestureRecognizer _longPressRecognizer;
- private readonly UITapGestureRecognizer _tapGestureRecognizer;
-
- public CutomImageRenderer()
- {
- _longPressRecognizer = new UILongPressGestureRecognizer((s) =>
- {
- if (s.State == UIGestureRecognizerState.Began && _view != null)
- {
- _view.LongPressedHandler?.Invoke(_view, null);
- var command = _view.LongpressCommand;
- command?.Execute(_view);
- }
- });
-
- _tapGestureRecognizer = new UITapGestureRecognizer(() =>
- {
-
- _view.TappedHandler?.Invoke(_view, null);
- var command = _view.Command;
- command?.Execute(_view);
- });
- }
-
- protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
- {
- base.OnElementChanged(e);
-
- if (e.NewElement != null)
- {
- _view = e.NewElement as CustomImage;
- }
-
-
-
- if (Control != null)
- {
- Control.UserInteractionEnabled = true;
- Control.AddGestureRecognizer(_longPressRecognizer);
- Control.AddGestureRecognizer(_tapGestureRecognizer);
- }
- }
- }
- }
Now create a Model for images so that we can use it for our list of images:
- public class ImageModel
- {
- public string Image { get; set; }
- public int ImageId { get; set; }
- }
After that create a view model for main page,
- public class ImageVM
- {
- public ImageVM()
- {
- ImageModels = new List<ImageModel>
- {
- new ImageModel{ImageId=1,Image="download.jfif"},
- new ImageModel{ImageId=2,Image="download1.jfif"},
- new ImageModel{ImageId=3,Image="download2.jfif"},
- new ImageModel{ImageId=4,Image="download3.png"},
- new ImageModel{ImageId=5,Image="download4.jfif"},
- new ImageModel{ImageId=6,Image="download.jfif"},
- new ImageModel{ImageId=7,Image="download5.png"},
- new ImageModel{ImageId=8,Image="images6.jfif"},
- new ImageModel{ImageId=9,Image="images7.jfif"},
- new ImageModel{ImageId=10,Image="images8.jfif"},
- };
- }
- public ICommand LongPressCommand
- {
- get
- {
- return new Command(() =>
- {
- App.Current.MainPage.DisplayAlert("LongPress Command", "This is long press command", "OK");
- });
- }
- }
- public ICommand TappCommand
- {
- get
- {
- return new Command(() =>
- {
- App.Current.MainPage.DisplayAlert("Tapp Command", "This is Tap command", "OK");
- });
- }
- }
- public List<ImageModel> ImageModels { get; set; }
- }
Here we create a list of ImageModels that we will bind with listview in our main page, we also create two commands - one for longpress and the other one for single tap which we will bind with image.
Now that we have our 2 (Android and IOS) implementations, let’s use it in our XAML!
- <?xml version="1.0" encoding="utf-8" ?>
- <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
- xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- xmlns:d="http://xamarin.com/schemas/2014/forms/design"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:customimage="clr-namespace:LongpressSample.CustomControl"
- mc:Ignorable="d"
- xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
- ios:Page.UseSafeArea="true"
- x:Class="LongpressSample.MainPage">
-
- <StackLayout>
- <Label Text="Image with binable Command" HorizontalOptions="CenterAndExpand" FontSize="Medium"/>
- <customimage:CustomImage Source="download.jfif" Aspect="AspectFit" HeightRequest="100" WidthRequest="100"
- Command="{Binding TappCommand}"
- LongpressCommand="{Binding LongPressCommand}">
-
- </customimage:CustomImage>
- <Label Text="List of Images with events" HorizontalOptions="CenterAndExpand" FontSize="Medium"/>
- <ListView ItemsSource="{Binding ImageModels}" HasUnevenRows="True" RowHeight="50">
- <ListView.ItemTemplate>
- <DataTemplate>
- <ViewCell>
- <StackLayout>
- <customimage:CustomImage ClassId="{Binding ImageId}" Source="{Binding Image}" HorizontalOptions="Start"
- Aspect="Fill" HeightRequest="150" WidthRequest="150"
- LongPressed="CustomImage_LongPressed"
- Tapped="CustomImage_Tapped">
-
- </customimage:CustomImage>
- </StackLayout>
- </ViewCell>
- </DataTemplate>
- </ListView.ItemTemplate>
- </ListView>
- </StackLayout>
-
- </ContentPage>
Now .cs code of the main page where we set BindingContext for page and implement events for Image .
- public partial class MainPage : ContentPage
- {
- public MainPage()
- {
- InitializeComponent();
- BindingContext = new ImageVM();
- }
-
- private void CustomImage_Tapped(object sender, EventArgs e)
- {
- var image = (sender) as Image;
- DisplayAlert("Tapped Event",$"You Tapped on image {image.ClassId}", "OK");
- }
-
- private void CustomImage_LongPressed(object sender, EventArgs e)
- {
- var image = (sender) as Image;
- DisplayAlert("Longpress Event", $"Longpress on image {image.ClassId}", "OK");
- }
- }
And that's it
Note
Commands for Image will not work in Listview or Collectionview
Please leave your comment if you have any query.
if you want to give any suggestions or if you think i have to change or improve the code, I would be happy to hear from you .
Thank you!
Output