Siddhant maurya
Siddhant maurya

Reputation: 293

when i add tap gesture the pinch and pan does not work in xamarin forms

I want to create a functionality to tap and add a pin image over another background image and the background image should be able to zoom and pan this is the XAML code for this, here the pinch zoom is not working but tap event is working fine

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:local="clr-namespace:POC"
         xmlns:ui="clr-namespace:Vapolia.Lib.Ui;assembly=XamarinFormsGesture"
         x:Class="POC.MainPage"
         Title="Main Page">
<ScrollView AbsoluteLayout.LayoutFlags="All">
    <local:PinchAndPanContainer>
        <local:PinchAndPanContainer.Content >
            <AbsoluteLayout x:Name="AbsoluteLayoutForImage">
                <Image x:Name="FloorPlanImage" 
                Source="Capture2.png"
                HeightRequest="400"
                IsEnabled="True"
                InputTransparent="True"
                ui:Gesture.TapCommand2="{Binding TapCommand2}"/>//This Property
            </AbsoluteLayout>
        </local:PinchAndPanContainer.Content>
    </local:PinchAndPanContainer>
</ScrollView>

in the cs file, this tap command is adding a pin image inside the absolute layout using the coordinates in Point.

public Command<Point> TapCommand2 => new Command<Point>(point =>
    {
        AddPin(point);
    });

Now if we just remove ui:Gesture.TapCommand2="{Binding TapCommand2}" this property from the above code pinch and pan works fine.

For Tap event I used Vapolia.XamarinFormsGesture NuGet package and for pinch and pan used xamarin forms Gesture Recognizer Can anyone help

Upvotes: 3

Views: 2130

Answers (2)

charles young
charles young

Reputation: 2289

I used the solution from FreakyAli in my MVVM app. I just added his code as a .cs file in the ViewModels folder and referenced the new class in my XAML:

<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage x:TypeArguments="viewModels:ImageViewModel"
                      xmlns="http://xamarin.com/schemas/2014/forms"
                      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                      xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
                      xmlns:viewModels="clr-namespace:BLE.Client.ViewModels;assembly=BLE.Client"
                      x:Class="BLE.Client.Pages.ImagePage" Title="View Image">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
      
    <StackLayout Grid.Row="0" Orientation="Horizontal" >
      <viewModels:ZoomableImage x:Name="WaypointImage"
             Source="{Binding MyImage}"
             HorizontalOptions="FillAndExpand">
      </viewModels:ZoomableImage>
    </StackLayout>
  </Grid>
</views:MvxContentPage>

C#:

   private ImageSource _myImage;
   public ImageSource MyImage
   {
      get => _myImage;
      set
      {
         _myImage = value;
         RaisePropertyChanged(() => MyImage);
      }
   }

Upvotes: 0

FreakyAli
FreakyAli

Reputation: 16459

Recently I did a similar functionality and ended up creating a CustomControl as below:

Note: I have used the FFImageLoadings CachedImage in my class in case you are not using FFImage just replace it with your defualt xamarin forms image.

And this has the following functionalities: PanSwipe, Zoom and DoubleTap to zoom.

using System;
using Xamarin.Forms;
using FFImageLoading.Forms;

public class ZoomableImage : CachedImage //In case not using ff image replace this with the Image control
{
    private const double MIN_SCALE = 1;
    private const double MAX_SCALE = 4;
    private const double OVERSHOOT = 0.15;
    private double StartScale, LastScale;
    private double StartX, StartY;

    public ZoomableImage()
    {
        var pinch = new PinchGestureRecognizer();
        pinch.PinchUpdated += OnPinchUpdated;
        GestureRecognizers.Add(pinch);

        var pan = new PanGestureRecognizer();
        pan.PanUpdated += OnPanUpdated;
        GestureRecognizers.Add(pan);

        var tap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
        tap.Tapped += OnTapped;
        GestureRecognizers.Add(tap);

        Scale = MIN_SCALE;
        TranslationX = TranslationY = 0;
        AnchorX = AnchorY = 0;
    }

    protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
    {
        Scale = MIN_SCALE;
        TranslationX = TranslationY = 0;
        AnchorX = AnchorY = 0;
        return base.OnMeasure(widthConstraint, heightConstraint);
    }

    private void OnTapped(object sender, EventArgs e)
    {
        if (Scale > MIN_SCALE)
        {
            this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
            this.TranslateTo(0, 0, 250, Easing.CubicInOut);
        }
        else
        {
            AnchorX = AnchorY = 0.5; //TODO tapped position
            this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
        }
    }

    private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
    {
        switch (e.StatusType)
        {
            case GestureStatus.Started:
                StartX = (1 - AnchorX) * Width;
                StartY = (1 - AnchorY) * Height;
                break;
            case GestureStatus.Running:
                AnchorX = Clamp(1 - (StartX + e.TotalX) / Width, 0, 1);
                AnchorY = Clamp(1 - (StartY + e.TotalY) / Height, 0, 1);
                break;
        }
    }

    private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
    {
        switch (e.Status)
        {
            case GestureStatus.Started:
                LastScale = e.Scale;
                StartScale = Scale;
                AnchorX = e.ScaleOrigin.X;
                AnchorY = e.ScaleOrigin.Y;
                break;
            case GestureStatus.Running:
                if (e.Scale < 0 || Math.Abs(LastScale - e.Scale) > (LastScale * 1.3) - LastScale)
                { return; }
                LastScale = e.Scale;
                var current = Scale + (e.Scale - 1) * StartScale;
                Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT));
                break;
            case GestureStatus.Completed:
                if (Scale > MAX_SCALE)
                    this.ScaleTo(MAX_SCALE, 250, Easing.SpringOut);
                else if (Scale < MIN_SCALE)
                    this.ScaleTo(MIN_SCALE, 250, Easing.SpringOut);
                break;
        }
    }

    private T Clamp<T>(T value, T minimum, T maximum) where T: IComparable
    {
        if (value.CompareTo(minimum) < 0)
            return minimum;
        else if (value.CompareTo(maximum) > 0)
            return maximum;
        else
            return value;
    }
}

Good luck, In case of queries kindly revert.

Upvotes: 4

Related Questions