-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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