Skip to content

Instantly share code, notes, and snippets.

@arashadbm
Forked from sgraf812/PanAndZoomBehavior.cs
Last active January 28, 2016 16:18
Show Gist options
  • Save arashadbm/10737606 to your computer and use it in GitHub Desktop.
Save arashadbm/10737606 to your computer and use it in GitHub Desktop.
Pan and zoom behavior for Windows Phone 8 with DoupleTap to zoom in/out and Fixed Translation Issue when zoomed in .No pan inertia/squish.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using Microsoft.Phone.Controls;
using System.Windows.Media.Animation;
using System;
//Original behavior in this Link : https://gist.github.com/sgraf812/7033464#file-panandzoombehavior
//In this version:
//I switched to Manipulation Events and removed the dependency on GestureListener because it's now obselete
//Fixed Translation Issue when zoomed in
//Added DoupleTap to zoomIn and zoomOut
namespace WP.Behaviors
{
public class PanAndZoomBehavior : Behavior<FrameworkElement>
{
private const double MinZoomLevel = 1.0;
/// <summary>
/// This does not enforce zoom bounds on setting.
/// Default is 10.0
/// To Disable Zooming set the value to 1.0
/// Changing this value will not change the current zoom value but will prevent next zooming from exceeding this max value
/// </summary>
public double MaxZoomLevel { get; set; }
/// <summary>
/// The desired Zoom level after User DoubleTap the current image and the zoom level was 1.0
/// Default is 2.0
/// If DoubleTapZoomLevel is greater than MaxZoomLevel, Image will be zoomed only to MaxZoomLevel
/// If DoubleTapZoomLevel is less than or equal to one, Douple tap will not zoom in
/// </summary>
public double DoubleTapZoomLevel { get; set; }
public PanAndZoomBehavior()
{
MaxZoomLevel = 10.0;
DoubleTapZoomLevel = 2;
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.ManipulationDelta += AssociatedObject_ManipulationDelta;
AssociatedObject.DoubleTap += AssociatedObject_DoubleTap;
AssociatedObject.RenderTransform = new CompositeTransform();
// wait for the RootVisual to be initialized
Dispatcher.BeginInvoke(() =>
((PhoneApplicationFrame)Application.Current.RootVisual).OrientationChanged += OrientationChanged);
}
protected override void OnDetaching()
{
((PhoneApplicationPage)Application.Current.RootVisual).OrientationChanged -= OrientationChanged;
AssociatedObject.ManipulationDelta -= AssociatedObject_ManipulationDelta;
AssociatedObject.DoubleTap -= AssociatedObject_DoubleTap;
base.OnDetaching();
}
private void AssociatedObject_DoubleTap(object sender, System.Windows.Input.GestureEventArgs e)
{
FrameworkElement img = sender as FrameworkElement;
CompositeTransform transform = img.RenderTransform as CompositeTransform;
if (transform.ScaleX > MinZoomLevel)
{
//Zoom out (reset)
AssociatedObject.RenderTransform = new CompositeTransform();
}
else
{
//check DoubleTapZoomLevel against the MinZoomLevel and MaxZoomLevel
double zoomLevel = Math.Max(Math.Min(DoubleTapZoomLevel, MaxZoomLevel), MinZoomLevel);
if (zoomLevel == MinZoomLevel) return;//No need to zoom
// Zoom In
var point = e.GetPosition(img);
var scale = new CompositeTransform
{
CenterX = point.X,
CenterY = point.Y,
ScaleX = Clamp(DoubleTapZoomLevel,
MinZoomLevel / transform.ScaleX,
MaxZoomLevel / transform.ScaleX),
ScaleY = Clamp(DoubleTapZoomLevel,
MinZoomLevel / transform.ScaleY,
MaxZoomLevel / transform.ScaleY)
};
ConstrainToParentBounds(img, scale);
transform = ComposeScaleTranslate(transform, scale);
img.RenderTransform = transform;
}
}
private void AssociatedObject_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e)
{
var img = sender as FrameworkElement;
var transform = img.RenderTransform as CompositeTransform;
if (e.PinchManipulation != null)
{
//Zooming
var scale = new CompositeTransform
{
CenterX = e.PinchManipulation.Original.Center.X,
CenterY = e.PinchManipulation.Original.Center.Y,
ScaleX = Clamp(e.PinchManipulation.DeltaScale,
MinZoomLevel / transform.ScaleX,
MaxZoomLevel / transform.ScaleX),
ScaleY = Clamp(e.PinchManipulation.DeltaScale,
MinZoomLevel / transform.ScaleY,
MaxZoomLevel / transform.ScaleY)
};
ConstrainToParentBounds(img, scale);
transform = ComposeScaleTranslate(transform, scale);
img.RenderTransform = transform;
}
else if (e.DeltaManipulation != null)
{
//Panning
var translate = new CompositeTransform
{
//Multiply by transform.ScaleX to speed up panning when you are zoomed in
TranslateX = e.DeltaManipulation.Translation.X * transform.ScaleX,
TranslateY = e.DeltaManipulation.Translation.Y * transform.ScaleY
};
ConstrainToParentBounds(img, translate);
img.RenderTransform = ComposeScaleTranslate(transform, translate);
}
}
private void OrientationChanged(object sender, OrientationChangedEventArgs e)
{
// Handling orientation change is a heck more involved than I initially thought
AssociatedObject.RenderTransform = new CompositeTransform();
}
#region helper methods
private static void ConstrainToParentBounds(FrameworkElement elm, CompositeTransform transform)
{
var p = (FrameworkElement)elm.Parent;
var visual = p.TransformToVisual(elm);
var canvas = p.TransformToVisual(elm).TransformBounds(new Rect(0, 0, p.ActualWidth, p.ActualHeight));
// Now compute the new viewport relative to the previous
var newViewport = transform.TransformBounds(new Rect(0, 0, elm.ActualWidth, elm.ActualHeight));
var top = newViewport.Top - canvas.Top;
var bottom = canvas.Bottom - newViewport.Bottom;
var left = newViewport.Left - canvas.Left;
var right = canvas.Right - newViewport.Right;
if (top > 0)
if (top + bottom > 0)
transform.TranslateY += (bottom - top) / 2;
else
transform.TranslateY -= top;
else if (bottom > 0)
if (top + bottom > 0)
transform.TranslateY += (bottom - top) / 2;
else
transform.TranslateY += bottom;
if (left > 0)
if (left + right > 0)
transform.TranslateX += (right - left) / 2;
else
transform.TranslateX -= left;
else if (right > 0)
if (left + right > 0)
transform.TranslateX += (right - left) / 2;
else
transform.TranslateX += right;
}
private static CompositeTransform ComposeScaleTranslate(CompositeTransform fst, CompositeTransform snd)
{
// See http://stackoverflow.com/a/19439099/388010 on why this works
return new CompositeTransform
{
ScaleX = fst.ScaleX * snd.ScaleX,
ScaleY = fst.ScaleY * snd.ScaleY,
CenterX = fst.CenterX,
CenterY = fst.CenterY,
TranslateX = snd.TranslateX + snd.ScaleX * fst.TranslateX + (snd.ScaleX - 1) * (fst.CenterX - snd.CenterX),
TranslateY = snd.TranslateY + snd.ScaleY * fst.TranslateY + (snd.ScaleY - 1) * (fst.CenterY - snd.CenterY),
};
}
private static double Clamp(double val, double min, double max)
{
return val > min ? val < max ? val : max : min;
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment