I'm working on a full screen image page that supports pinch to zoom, pan to move and tap to show captions. I'm basing this on how image viewer works in apps such as Facebook and Yelp. My code is built off Xamarin examples on gesture recognizers, which can be found at https://developer.xamarin.com/guides/xamarin-forms/user-interface/gestures/
My problem is that when the image is zoomed in and I rotate the device, and then zoomed out. The image is off the center. I would really appreciate it very much if someone can help me finish this, so it supports varying device orientations.
using System; using System.ComponentModel; using Xamarin.Forms; namespace TurfDoctor { public class FullScreenImagePage : ContentPage { double currentScale = 1; double startScale = 1; double xOffset = 0; double yOffset = 0; double originalWidth; double originalHeight; double ScreenWidth; double ScreenHeight; PanGestureRecognizer panGesture; bool showEverything = false; StackLayout imageDescription; Button backButton; BoxView topBox; Image image; ContentView imageContainer; Label indexLabel; //Label xLabel, yLabel, transXLabel, transYLabel, widthLabel, heightLabel, scaleLabel, screenWidthLabel, screenHeightLabel; AbsoluteLayout absoluteLayout; protected override void OnAppearing () { ShowEverything = true; base.OnAppearing (); } protected override bool OnBackButtonPressed () { App.NavPage.BarTextColor = Color.Black; // turn the status bar back to black return base.OnBackButtonPressed (); } public bool ShowEverything { set{ showEverything = value; backButton.IsVisible = showEverything; imageDescription.IsVisible = showEverything; topBox.IsVisible = showEverything; indexLabel.IsVisible = showEverything; if (!showEverything) { // hide the status bar by turning it black App.NavPage.BarTextColor = Color.Black; imageContainer.GestureRecognizers.Add (panGesture); } else { // show the status bar by turning it white App.NavPage.BarTextColor = Color.White; imageContainer.GestureRecognizers.Remove (panGesture); } } get{ return showEverything; } } public FullScreenImagePage (String ImageName, string DescriptionText, int index, int count) { NavigationPage.SetHasNavigationBar (this, false); image = new Image { HorizontalOptions = LayoutOptions.CenterAndExpand, VerticalOptions = LayoutOptions.CenterAndExpand, Aspect = Aspect.AspectFill, Source = ImageName }; imageContainer = new ContentView { Content = image }; var tapGesture = new TapGestureRecognizer (); tapGesture.Tapped += OnTapped; imageContainer.GestureRecognizers.Add (tapGesture); var pinchGesture = new PinchGestureRecognizer (); pinchGesture.PinchUpdated += OnPinchUpdated; imageContainer.GestureRecognizers.Add (pinchGesture); panGesture = new PanGestureRecognizer (); panGesture.PanUpdated += OnPanUpdated; imageContainer.GestureRecognizers.Add (panGesture); absoluteLayout = new AbsoluteLayout { BackgroundColor = MyAppStyle.blackColor, }; var label = new Label { Text = DescriptionText, TextColor = MyAppStyle.whiteColor, FontAttributes = FontAttributes.Bold, FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)) }; var separator = new BoxView() { HeightRequest = 1, BackgroundColor = MyAppStyle.whiteColor}; imageDescription = new StackLayout { Padding = new Thickness(20), HorizontalOptions = LayoutOptions.Fill, Orientation = StackOrientation.Vertical, Children = { label, separator} }; backButton = new Button { Text = "Back", WidthRequest = 80, HeightRequest = 40, TextColor = MyAppStyle.whiteColor, FontAttributes = FontAttributes.Bold }; backButton.Clicked += (object sender, EventArgs e) => { OnBackButtonPressed(); Navigation.PopAsync(); }; indexLabel = new Label { Text = (index + 1).ToString () + " of " + count.ToString (), TextColor = MyAppStyle.whiteColor, FontAttributes = FontAttributes.Bold, HorizontalTextAlignment = TextAlignment.Center }; AbsoluteLayout.SetLayoutFlags (imageContainer, AbsoluteLayoutFlags.All); AbsoluteLayout.SetLayoutBounds (imageContainer, new Rectangle (0f, 0f, 1f, 1f)); absoluteLayout.Children.Add (imageContainer); AbsoluteLayout.SetLayoutFlags (imageDescription, AbsoluteLayoutFlags.PositionProportional | AbsoluteLayoutFlags.WidthProportional); AbsoluteLayout.SetLayoutBounds (imageDescription, new Rectangle(0f, 1f, 1f, AbsoluteLayout.AutoSize)); absoluteLayout.Children.Add(imageDescription); topBox = new BoxView { Color = MyAppStyle.blackColor, Opacity = 0.5 }; AbsoluteLayout.SetLayoutFlags (topBox, AbsoluteLayoutFlags.WidthProportional); AbsoluteLayout.SetLayoutBounds (topBox, new Rectangle(0f, 0f, 1f, 50f)); absoluteLayout.Children.Add (topBox); AbsoluteLayout.SetLayoutFlags (backButton, AbsoluteLayoutFlags.None); AbsoluteLayout.SetLayoutBounds (backButton, new Rectangle(0f, 10f, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize)); absoluteLayout.Children.Add (backButton); AbsoluteLayout.SetLayoutFlags (indexLabel, AbsoluteLayoutFlags.XProportional); AbsoluteLayout.SetLayoutBounds (indexLabel, new Rectangle(.5f, 20f, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize)); absoluteLayout.Children.Add (indexLabel); Content = absoluteLayout; } protected override void OnSizeAllocated(double width, double height) { base.OnSizeAllocated(width, height); //must be called if (ScreenWidth != width || ScreenHeight != height) { absoluteLayout.ForceLayout(); originalWidth = imageContainer.Content.Width / imageContainer.Content.Scale; originalHeight = imageContainer.Content.Height / imageContainer.Content.Scale; ScreenWidth = width; ScreenHeight = height; xOffset = imageContainer.Content.TranslationX; yOffset = imageContainer.Content.TranslationY; currentScale = imageContainer.Content.Scale; } } void OnTapped(object sender, EventArgs e) { ShowEverything = !ShowEverything; } void OnPanUpdated (object sender, PanUpdatedEventArgs e) { var s = (ContentView)sender; // do not allow pan if the image is in its intial size if (currentScale == 1) return; switch (e.StatusType) { case GestureStatus.Running: double xTrans = xOffset + e.TotalX, yTrans = yOffset + e.TotalY; // do not allow verical scorlling unless the image size is bigger than the screen s.Content.TranslateTo (xTrans, yTrans, 0, Easing.Linear); break; case GestureStatus.Completed: // Store the translation applied during the pan xOffset = s.Content.TranslationX; yOffset = s.Content.TranslationY; // center the image if the width of the image is smaller than the screen width if (originalWidth * currentScale < ScreenWidth && ScreenWidth > ScreenHeight) xOffset = (ScreenWidth - originalWidth*currentScale)/2 - s.Content.X; else xOffset = Math.Max (Math.Min (0, xOffset), -Math.Abs (originalWidth * currentScale - ScreenWidth)); // center the image if the height of the image is smaller than the screen height if (originalHeight * currentScale < ScreenHeight && ScreenHeight > ScreenWidth) yOffset = (ScreenHeight - originalHeight*currentScale)/2 - s.Content.Y; else yOffset = Math.Max (Math.Min ((originalHeight - ScreenHeight)/2, yOffset), -Math.Abs(originalHeight*currentScale - ScreenHeight - (originalHeight - ScreenHeight)/2)); // bounce the image back to inside the bounds s.Content.TranslateTo (xOffset, yOffset, 500, Easing.BounceOut); break; } } void OnPinchUpdated (object sender, PinchGestureUpdatedEventArgs e) { var s = (ContentView)sender; if (e.Status == GestureStatus.Started) { // Store the current scale factor applied to the wrapped user interface element, // and zero the components for the center point of the translate transform. startScale = s.Content.Scale; s.Content.AnchorX = 0; s.Content.AnchorY = 0; } if (e.Status == GestureStatus.Running) { // Calculate the scale factor to be applied. currentScale += (e.Scale - 1) * startScale; currentScale = Math.Max (1, currentScale); currentScale = Math.Min (currentScale, 5); //scaleLabel.Text = "Scale: " + currentScale.ToString (); if (currentScale == 1) ShowEverything = true; else ShowEverything = false; // The ScaleOrigin is in relative coordinates to the wrapped user interface element, // so get the X pixel coordinate. double renderedX = s.Content.X + xOffset; double deltaX = renderedX / ScreenWidth; double deltaWidth = ScreenWidth / (s.Content.Width * startScale); double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth; // The ScaleOrigin is in relative coordinates to the wrapped user interface element, // so get the Y pixel coordinate. double renderedY = s.Content.Y + yOffset; double deltaY = renderedY / ScreenHeight; double deltaHeight = ScreenHeight / (s.Content.Height * startScale); double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight; // Calculate the transformed element pixel coordinates. double targetX = xOffset - (originX * s.Content.Width) * (currentScale - startScale); double targetY = yOffset - (originY * s.Content.Height) * (currentScale - startScale); // Apply translation based on the change in origin. var transX = targetX.Clamp (-s.Content.Width * (currentScale - 1), 0); var transY = targetY.Clamp (-s.Content.Height * (currentScale - 1), 0); s.Content.TranslateTo (transX, transY, 0, Easing.Linear); // Apply scale factor. s.Content.Scale = currentScale; } if (e.Status == GestureStatus.Completed) { // Store the translation applied during the pan xOffset = s.Content.TranslationX; yOffset = s.Content.TranslationY; // center the image if the width of the image is smaller than the screen width if (originalWidth * currentScale < ScreenWidth && ScreenWidth > ScreenHeight) xOffset = (ScreenWidth - originalWidth*currentScale)/2 - s.Content.X; else xOffset = Math.Max (Math.Min (0, xOffset), -Math.Abs (originalWidth * currentScale - ScreenWidth)); // center the image if the height of the image is smaller than the screen height if (originalHeight * currentScale < ScreenHeight && ScreenHeight > ScreenWidth) yOffset = (ScreenHeight - originalHeight*currentScale)/2 - s.Content.Y; else yOffset = Math.Max (Math.Min ((originalHeight - ScreenHeight)/2, yOffset), -Math.Abs(originalHeight*currentScale - ScreenHeight - (originalHeight - ScreenHeight)/2)); // bounce the image back to inside the bounds s.Content.TranslateTo (xOffset, yOffset, 500, Easing.BounceOut); } } } }
@Roger, as I looked all over the web, all solutions seemed to be a bit too complicated and still lacking in functionality. Actually, all can be traced back to some original on the dev.xamarin forum, including the overcomplicated calculations. Instead of those, we can simply use the AnchorX/AnchorY properties. This is what I came up with in the end:
public class ZoomImage : Image { private const double MIN_SCALE = 1; private const double MAX_SCALE = 4; private const double OVERSHOOT = 0.15; private double StartScale; private double LastX, LastY; public ZoomImage() { 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) { if (Scale > MIN_SCALE) switch (e.StatusType) { case GestureStatus.Started: LastX = TranslationX; LastY = TranslationY; break; case GestureStatus.Running: TranslationX = Clamp(LastX + e.TotalX * Scale, -Width / 2, Width / 2); TranslationY = Clamp(LastY + e.TotalY * Scale, -Height / 2, Height / 2); break; } } private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e) { switch (e.Status) { case GestureStatus.Started: StartScale = Scale; AnchorX = e.ScaleOrigin.X; AnchorY = e.ScaleOrigin.Y; break; case GestureStatus.Running: double 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; } }
Extra functionality: zoom overshoot with springy bounce-back. Double tap in both directions: to jump to full zoom and restore the original scale. Unfortunately, some poor soul at Xamarin decided not to send co-ordinate info with the Tapped event (I can't fathom what they were thinking), so it jumps to full zoom centered now.
And the problems: I don't know if this is something universal or it only happens on my device but while zooming in from scale=1 works all right, pinching to zoom out at larger scales results in wildly out-of-tune, alternating e.Scale values coming in from Xamarin (something like alternating 0.7, 1.3, 0.6, 1.4 and so on, in quick succession, making the image flicker). It would be nice to know if somebody else can see this, too.
Apart from this, this code seems to work all right.
Spoke a bit too soon. :-) The corrected panning function:
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; } }
This uses the same anchor now as the zoom.
Answers
The code that handles device orientation change goes inside the following function according Xamarin documentation
https://developer.xamarin.com/guides/xamarin-forms/user-interface/layouts/device-orientation/
My problem is that when the image is zoomed in and I rotate the device, and then zoomed out. The image is off the center. It looks like this:
Here is the screenshot image:
Any updates? Did you solve this?
No. This thread sort of died.
Damn, so did you disable landscape mode on this page for you? Or what was your resolution?
Bit of an odd issue, when you look at the translationx and y for the imagecontainer, it's not correct. But simply correcting it to 0 doesn't fix the issue. Very odd. At first, I found this happens when you zoom (and even if you zoom back out) and then rotate. So let's say you zoom in, and then zoom back out to 0. It would still do it, I fixed that. What I still did not fix is when you zoom in and rotate, it positions the image not where it's supposed to. If I find a fix I'll let you know.
Wow, as I typed that, I was able to fix it by this:
imageContainer.Content.TranslateTo(0, 0, 0, Easing.Linear);
in the OnSizeAllocated method call.
The only other issue is that on landscape mode the bounds are not correct, even when I debug with a color for background, the background color takes up the acceptable bounds, but the pan bounds doesn't seem correct. Again, if I find a solution I'll let you know.
I'm not sure how to disable device rotation on a single page. So I disabled it for the whole app.
Can you post the entire OnSizedAllocated Method? I'm not sure it's working for me
Sure, so basically just resetting everything. (some code you don't really need to mind)
@DylanLiu If an image isn't the width of the screen, I'm seeing some weird behavior. Any thoughts?
Hi @DylanLiu ,
In your pinch update event, try dont initialize the AnchorX and AnchorY :
Replace this :
s.Content.AnchorX = 0; s.Content.AnchorY = 0;
by this :
//s.Content.AnchorX = 0; //s.Content.AnchorY = 0;
I hope that helps,
Mabrouk.
I tried that one before. Commenting out zeriong AnchorX and AnchorY breaks the pinch to zoom feature. I also tried zeroing the AnchorX and AnchorY in the OnSizedAllocated method, it made it worse.
I've tried it all as well. Probably spent days trying out various things. Nothing works besides resetting the image.
This is a very useful component. You should open a bug on https://bugzilla.xamarin.com , so that this can be addressed asap.
Lol asap? More like whenever they feel like doing it. I get your point though
@EmanueleSabetta
@JordanMax
I too spent days working on this ... and finally decided to give up and lock the screen orientation. I posted my code in hope somebody can fix it 100%.
Like I said my code was based on Xamarin documentation on gesture recognizer.
I added the bouncing off the edges stuff to make it behave more like facebook and yelp.
Well I did give you a way to fix it. If you reset the image it works perfectly. There's no performance hit either, it just readjusts the image. I've attached my OnSizeAllocated method above in case you missed it. The only problem right now that I see (except the reset workaround as I just mentioned) is that if the image isn't the full width of the screen, the zoom/pinch doesn't work as expected. Any insight on that?
I got the same problem. Unfortunately, I couldn't get it to work like @JordanMax . It works fine as long as I work on the same picture. A simple tap is reseting my picture to its original state. But if I load a new picture it gets offset to the top left corner. I also have the issue with turning the screen.
I also posted an issue on the github where the original Xamarin example is. But no response yet from Xamarin.
Have you guys had any more luck?
It will work if you follow what I did. What did you try? If you just reset everything on the image, it worked for me. We ended up doing away with landscape, not because of this but a completely unrelated reason. So I can't give you any more insight for I may have deleted the code. But I did post it above. Let me know what you tried.
I found out what my problem was. In the Xamarin example the AnchorX and Y are reset to 0. But they should be 0.5. My pictures appear now the way they should
I still have to figure out the rotation problem.
And I just found a solution to the screen rotation problem. I am resetting the content properties in OnPropertyChanging rather than OnSizeAllocated. That does the trick for me.
protected override void OnPropertyChanging(string propertyName = null) { base.OnPropertyChanging(propertyName); if (propertyName == "Width") { ResetView(); } }
I did some more modifications and produced a class derived from Image that I can use in the XAML file directly. I combined pinch and pan in one class. Rotation works as well as a reset on tapping once on the picture. One thing that I need to improve is the detection when the panning moves out of scope. The calculation there is not correct yet. Maybe someone can fix that for me
`using System;
using System.Diagnostics;
using Xamarin.Forms;
namespace LiveGantt.Helpers
{
public class PinchPanImage : Image
{
double _currentScale;
double _startPinchScale;
double _lastTransX;
double _lastTransY;
}
<?xml version="1.0" encoding="utf-8"?>
`
In iOS device, if device is rotated by 90 degrees(orientation changes), then the image bounds are getting changed and image is getting displayed in one of the corners. Someone please help me in fixing the orientation issues in iOS device. I am trying to work on orientation issues using protected override void OnSizeAllocated(double width, double height)
{
} but facing difficulties in calcuations of setting the image bounds properly when orientation of the device changes.
Note: In Android devices, it is working as expected.
@Roger, as I looked all over the web, all solutions seemed to be a bit too complicated and still lacking in functionality. Actually, all can be traced back to some original on the dev.xamarin forum, including the overcomplicated calculations. Instead of those, we can simply use the AnchorX/AnchorY properties. This is what I came up with in the end:
Extra functionality: zoom overshoot with springy bounce-back. Double tap in both directions: to jump to full zoom and restore the original scale. Unfortunately, some poor soul at Xamarin decided not to send co-ordinate info with the Tapped event (I can't fathom what they were thinking), so it jumps to full zoom centered now.
And the problems: I don't know if this is something universal or it only happens on my device but while zooming in from scale=1 works all right, pinching to zoom out at larger scales results in wildly out-of-tune, alternating e.Scale values coming in from Xamarin (something like alternating 0.7, 1.3, 0.6, 1.4 and so on, in quick succession, making the image flicker). It would be nice to know if somebody else can see this, too.
Apart from this, this code seems to work all right.
Spoke a bit too soon. :-) The corrected panning function:
This uses the same anchor now as the zoom.
I implemented your solution and it actually works great. One thing though, if an image is not the width of the screen, I'm seeing weird behavior when zoomed in and panning on landscape mode. Portrait it's fine, can't seem to understand why. Any thoughts?
@djgtram, I see the same wild behavior on the e.Scale values. I updated your code to account for this by throwing out any value that varies by more than a certain percentage from the previous value I got. Smoothes out the zooming quite a bit.
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) { // e.Scale sometimes returns wildly different values from one update to the next. This causes flickering // By removing values that are too far off, we smooth that out. 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; } }
Just FYI, I put the an array of FullScreenImagePage in a carouselPage. And there was problem of swiping through images while the it is zoomed out. Here is the fix:
@djgtram @DaveRobinder.3226 the weird flickering while zooming that you are referring to, is it happening on android or iOS?
I'm experiencing it on android. And it actually makes the image completely disappear if I don't move my fingers very slowsly
@djgtram
I am using your ZoomImage as the second row in a two row grid with the first row being a segmented control. The image initially displays properly. I don't mind that it covers up the segmented control when zoomed out.
When the user changes the segment, I change the Source of the ZoomImage and the image ends up centered on 0,0 of the screen.
Any ideas?
When i'm using the ZoomImage solution of @djgtram the image positions itself in the top left corner and not completely visible i'm adding the image to an empty contentPage
Can anyone help me out ?
Is anyone else having performance issues on android with this? IOS zooms smooth, but Android is very choppy.
With regards to the "choppy" visual behavior on Android, I may have some clues as to why this could be happening.
Firstly, in the Xamarin.Forms.Platform.Android.ImageRenderer, anytime the Source property is changed, the renderer sets the Source = null prior to the new value its expecting to receive. Setting Source = null causes the flicker. I don't want the Android.ImageRenderer to set the Source to null just because the value (bytes) are changing.
For reference, here is the bug that will probably NEVER get fixed by Xamarin Microsoft.
https://bugzilla.xamarin.com/show_bug.cgi?id=36600
I resolved this heinous issue using a custom renderer. I override the OnElementPropertyChanged and handle when the Source property is changing. Obviously I override OnElementChanged as well. In that override I create an instance of the Android.Widget.ImageView using the SetNativeControl API.
If you happen to see flickering (or choppy) behavior, try this and let me know if it worked for you or not.
The use-case for my application is a Xamarin.Forms.Image downloading a .png file (95KB) from a site every 1.5 seconds. Flicker would ensue, but the 'NoFlickerImageViewRenderer' saved me.
By the way, iOS does not have this problem.
You can also replace
Image
withCachedImage
from FFImageLoading (it doesn't have that issue too). It will also auto-cancel all current image loading tasks if the source changing refresh rate is high.I am having the same problem as well on iOS.

I fixed it by setting the preset AnchorX and AnchorY in the constructor to 0.5 insted of 0 as they suggested.
`
public class ZoomImage: Image {
private const double MIN_SCALE = 1;
private const double MAX_SCALE = 4;
private const double OVERSHOOT = 0.15;
private double StartScale, LastScale, StartX, StartY;
`
And also, if anyone is having problem of Pan Gesture in iOS with the MasterDetailsPage menu gesture, try setting the IsGestureEnabled property of your MasterDetailsPage page.
IsGestureEnabled = false
I had noticed that in Android Emulator, the panning is not correct. I haven't tried it on an actual Android device yet.
On the other hand, the panning on iOS Simulator works correctly.
The only problem i am having right now with the double tap to zoom the image is it did not capture the touched position as the anchor. Everyone on the Internet i found claimed that the GestureRecognizers does not provide you the touch positions unless you make a custom renderer, but I m not sure how to get the touch positions in the renderer.
Hi GregCadmes,
How can I use this control ?
If I put it in the PCL it requires me to add android platform dlls etc
@MeteorsoftWindows If you want to get the touch position through a custom renderer, I recommend that you take a look at an example of a paint app. Like this one: https://github.com/MitchMilam/Drawit