Last active
October 27, 2018 11:47
-
-
Save asozyurt/6b637b5c3425791814746a57d7608bd5 to your computer and use it in GitHub Desktop.
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
/* | |
Ahmet Selçuk Özyurt | |
13.03.2017 | |
www.asozyurt.com | |
*/ | |
using System; | |
using UniversalistDergiRC.Repositories; | |
using Xamarin.Forms; | |
namespace UniversalistDergiRC.Core | |
{ | |
public class ZoomImageBehavior : Behavior<View> | |
{ | |
#region Fields | |
private View _associatedObject; | |
private double _currentScale = 1, _startScale = 1, _xOffset, _yOffset; | |
private PanGestureRecognizer _panGestureRecognizer; | |
private ContentView _parent; | |
private PinchGestureRecognizer _pinchGestureRecognizer; | |
private TapGestureRecognizer _tapGestureRecognizer; | |
long panStartTime, panEndTime; | |
double panStartX, panStartY; | |
#endregion | |
public double Clamp(double self, double min, double max) | |
{ | |
return Math.Min(max, Math.Max(self, min)); | |
} | |
internal void ResetToDefaultPosition() | |
{ | |
_parent.Content.TranslationX = 0; | |
_parent.Content.TranslationY = 0; | |
_parent.Content.Scale = 1; | |
_currentScale = 1; | |
_xOffset = 0; | |
_yOffset = 0; | |
} | |
protected override void OnAttachedTo(View associatedObject) | |
{ | |
InitializeRecognizers(); | |
_associatedObject = associatedObject; | |
InitializeEvents(); | |
base.OnAttachedTo(associatedObject); | |
} | |
protected override void OnDetachingFrom(View associatedObject) | |
{ | |
CleanupEvents(); | |
_parent = null; | |
_pinchGestureRecognizer = null; | |
_panGestureRecognizer = null; | |
_tapGestureRecognizer = null; | |
_associatedObject = null; | |
base.OnDetachingFrom(associatedObject); | |
} | |
private void AssociatedObjectBindingContextChanged(object sender, EventArgs e) | |
{ | |
if (_associatedObject == null && (sender as Image) != null) | |
_associatedObject = sender as Image; | |
_parent = _associatedObject.Parent as ContentView; | |
_parent?.GestureRecognizers.Remove(_panGestureRecognizer); | |
_parent?.GestureRecognizers.Add(_panGestureRecognizer); | |
_parent?.GestureRecognizers.Remove(_pinchGestureRecognizer); | |
_parent?.GestureRecognizers.Add(_pinchGestureRecognizer); | |
_parent?.GestureRecognizers.Remove(_tapGestureRecognizer); | |
_parent?.GestureRecognizers.Add(_tapGestureRecognizer); | |
} | |
/// <summary> | |
/// Cleanup the events. | |
/// </summary> | |
private void CleanupEvents() | |
{ | |
_pinchGestureRecognizer.PinchUpdated -= OnPinchUpdated; | |
_panGestureRecognizer.PanUpdated -= OnPanUpdated; | |
_tapGestureRecognizer.Tapped -= OnTapUpdated; | |
_associatedObject.BindingContextChanged -= AssociatedObjectBindingContextChanged; | |
} | |
/// <summary> | |
/// Initialise the events. | |
/// </summary> | |
private void InitializeEvents() | |
{ | |
CleanupEvents(); | |
_pinchGestureRecognizer.PinchUpdated += OnPinchUpdated; | |
_panGestureRecognizer.PanUpdated += OnPanUpdated; | |
_tapGestureRecognizer.Tapped += OnTapUpdated; | |
_associatedObject.BindingContextChanged += AssociatedObjectBindingContextChanged; | |
AssociatedObjectBindingContextChanged(_associatedObject, null); | |
} | |
private void InitializeRecognizers() | |
{ | |
_pinchGestureRecognizer = new PinchGestureRecognizer(); | |
_panGestureRecognizer = new PanGestureRecognizer(); | |
_tapGestureRecognizer = new TapGestureRecognizer | |
{ | |
NumberOfTapsRequired = 2 | |
}; | |
} | |
private void OnPanUpdated(object sender, PanUpdatedEventArgs e) | |
{ | |
if (_parent == null || !IsTranslateEnabled) | |
{ | |
return; | |
} | |
switch (e.StatusType) | |
{ | |
case GestureStatus.Started: | |
panStartX = _xOffset; | |
panStartY = _yOffset; | |
panStartTime = DateTime.Now.Ticks; | |
break; | |
case GestureStatus.Running: | |
_parent.Content.TranslationX = _xOffset + e.TotalX; | |
_parent.Content.TranslationY = _yOffset + e.TotalY; | |
break; | |
case GestureStatus.Completed: | |
_xOffset = _parent.Content.TranslationX; | |
_yOffset = _parent.Content.TranslationY; | |
if (_currentScale == 1) | |
{ | |
panEndTime = DateTime.Now.Ticks; | |
if (panEndTime - panStartTime < 1500000) | |
{ | |
double yDifference = panStartY - _yOffset; | |
double xDifference = panStartX - _xOffset; | |
if (yDifference > -100 && yDifference < 100 | |
&& (xDifference < -35 || xDifference > 35)) | |
{ | |
// If x is bigger than 0 sliding direction is right, otherwise left | |
MessagingCenter.Send(this, xDifference > 0 ? Constants.RIGHT_SLIDE : Constants.LEFT_SLIDE); | |
} | |
panStartX = 0; | |
panStartY = 0; | |
} | |
} | |
break; | |
} | |
} | |
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e) | |
{ | |
if (_parent == null || !IsScaleEnabled) | |
{ | |
return; | |
} | |
switch (e.Status) | |
{ | |
case GestureStatus.Started: | |
_startScale = _parent.Content.Scale; | |
_parent.Content.AnchorX = 0; | |
_parent.Content.AnchorY = 0; | |
break; | |
case GestureStatus.Running: | |
_currentScale += (e.Scale - 1) * _startScale; | |
_currentScale = Math.Max(1, _currentScale); | |
var renderedX = _parent.Content.X + _xOffset; | |
var deltaX = renderedX / _parent.Width; | |
var deltaWidth = _parent.Width / (_parent.Content.Width * _startScale); | |
var originX = (e.ScaleOrigin.X - deltaX) * deltaWidth; | |
var renderedY = _parent.Content.Y + _yOffset; | |
var deltaY = renderedY / _parent.Height; | |
var deltaHeight = _parent.Height / (_parent.Content.Height * _startScale); | |
var originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight; | |
var targetX = _xOffset - (originX * _parent.Content.Width) * (_currentScale - _startScale); | |
var targetY = _yOffset - (originY * _parent.Content.Height) * (_currentScale - _startScale); | |
_parent.Content.TranslationX = Clamp(targetX, -_parent.Content.Width * (_currentScale - 1), 0); | |
_parent.Content.TranslationY = Clamp(targetY, -_parent.Content.Height * (_currentScale - 1), 0); | |
_parent.Content.Scale = _currentScale; | |
break; | |
case GestureStatus.Completed: | |
_xOffset = _parent.Content.TranslationX; | |
_yOffset = _parent.Content.TranslationY; | |
break; | |
} | |
} | |
private void OnTapUpdated(object sender, EventArgs e) | |
{ | |
if (_parent == null || !IsScaleEnabled) | |
{ | |
return; | |
} | |
// If scale is 1 zoom in for 1.3 scale | |
if (_parent.Content.Scale == 1) | |
{ | |
OnPinchUpdated(this, new PinchGestureUpdatedEventArgs(GestureStatus.Started, 0, new Point(0, 0))); | |
OnPinchUpdated(this, new PinchGestureUpdatedEventArgs(GestureStatus.Running, 1.3, new Point(0, 0))); | |
OnPinchUpdated(this, new PinchGestureUpdatedEventArgs(GestureStatus.Completed, 0, new Point(0, 0))); | |
} | |
// If scale is not 1 set scale to 1 which is its first scale | |
else | |
{ | |
ResetToDefaultPosition(); | |
} | |
} | |
#region IsScaleEnabled property | |
public static readonly BindableProperty IsScaleEnabledProperty = | |
BindableProperty.Create("IsScaleEnabled", typeof(bool), typeof(ZoomImageBehavior), default(bool)); | |
//BindableProperty.Create<MultiTouchBehavior, bool>(w => w.IsScaleEnabled, default(bool)); | |
public bool IsScaleEnabled | |
{ | |
get { return (bool)GetValue(IsScaleEnabledProperty); } | |
set { SetValue(IsScaleEnabledProperty, value); } | |
} | |
#endregion | |
#region IsTranslateEnabled property | |
public static readonly BindableProperty IsTranslateEnabledProperty = | |
// BindableProperty.Create<MultiTouchBehavior, bool>(w => w.IsTranslateEnabled, default(bool)); | |
BindableProperty.Create("IsTranslateEnabled", typeof(bool), typeof(ZoomImageBehavior), default(bool)); | |
public bool IsTranslateEnabled | |
{ | |
get { return (bool)GetValue(IsTranslateEnabledProperty); } | |
set { SetValue(IsTranslateEnabledProperty, value); } | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment