Reputation: 147
i am trying to zoom in and zoom out on a content page using xamarin.forms. I am able zoom in and zoom out but the problem is scrolling is not working. i want zoom an image. with this code zooming is working perfectly. But while zooming i am not able to see full image. i must scroll to view the rest of the image. for that i need to scroll. but scrolling is not working.
XAML
xmlns:helper="clr-namespace:KPGTC.Deals.Mobile.Helpers"
<helper:PinchToZoomContainer>
<helper:PinchToZoomContainer.Content>
<Image x:Name="img_Popup"/>
</helper:PinchToZoomContainer.Content>
</helper:PinchToZoomContainer>
Code:
public class PinchToZoomContainer : ContentView
{
double MIN_SCALE = 1;
double MAX_SCALE = 4;
double startScale = 1;
double currentScale = 1;
double xOffset = 0;
double yOffset = 0;
bool _isActive = false;
public PinchToZoomContainer()
{
DependencyService.Get<IHelpers>().ShowAlert("Double-tap to zoom");
//var _pinchGesture = new PinchGestureRecognizer();
//_pinchGesture.PinchUpdated += OnPinchUpdated;
//GestureRecognizers.Add(_pinchGesture);
var _tapGesture = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
_tapGesture.Tapped += On_Tapped;
GestureRecognizers.Add(_tapGesture);
var _panGesture = new PanGestureRecognizer();
_panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(_panGesture);
TranslationX = 0;
TranslationY = 0;
_isActive = false;
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (_isActive)
{
if (e.TotalX > 0)
{
if (e.TotalX > 2)
{
TranslationX += 15;
}
}
else
{
if (e.TotalX < -2)
{
TranslationX -= 15;
}
}
}
}
private void On_Tapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
_isActive = false;
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
_isActive = true;
AnchorX = AnchorY = 0.5;
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
Upvotes: 0
Views: 879
Reputation: 9713
Alright, this was quite a tough one and admittedly I don't understand fully how I made it, but I made it.
Some thoughts:
15
every time the pan event was raised, but there is a better way: Just store the initial offset of the content and then add the TotalX
and TotalY
respectively to the TranslationX
and the TranslationY
of the content (this was the easy part)Here is the code for the panning:
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (e.StatusType == GestureStatus.Started)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
if (e.StatusType != GestureStatus.Completed
&& e.StatusType != GestureStatus.Canceled)
{
this.Content.TranslationX = this.xOffset + e.TotalX;
this.Content.TranslationY = this.yOffset + e.TotalY;
}
if (e.StatusType == GestureStatus.Completed)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
}
And here for the pinching
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
this.startScale = this.Content.Scale;
this.Content.AnchorX = 0;
this.Content.AnchorY = 0;
this.startScaleOrigin = e.ScaleOrigin;
}
if (e.Status == GestureStatus.Running)
{
var originDiff = PinchToZoomContainer.CalculateDiff(e.ScaleOrigin, this.startScaleOrigin);
this.currentScale += (e.Scale - 1) * this.startScale;
this.currentScale = Math.Max(1, this.currentScale);
double renderedX = this.Content.X + this.xOffset;
double deltaX = renderedX / this.Width;
double deltaWidth = this.Width / (this.Content.Width * this.startScale);
double originX = (this.startScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = this.Content.Y + this.yOffset;
double deltaY = renderedY / this.Height;
double deltaHeight = this.Height / (this.Content.Height * this.startScale);
double originY = (startScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = this.xOffset - ((originX) * this.Content.Width) * (this.currentScale - this.startScale) - originDiff.X * this.Content.Width;
double targetY = this.yOffset - ((originY) * this.Content.Height) * (this.currentScale - this.startScale) - originDiff.Y * this.Content.Height;
this.Content.TranslationX = targetX.Clamp(-this.Content.Width * (this.currentScale - 1), 0);
this.Content.TranslationY = targetY.Clamp(-this.Content.Height * (this.currentScale - 1), 0);
this.Content.Scale = this.currentScale;
}
if (e.Status == GestureStatus.Completed)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
}
(Of course you have to add Point startScaleOrigin
to your class).
Finally you need the method to calculate the distance between two points
private static Point CalculateDiff(Point first, Point second)
{
return second.Offset(-first.X, -first.Y);
}
Unfortunately I did not manage to get the tapping right, but I think you should be able to figure it out from here.
Upvotes: 1