-
-
Save Shiggiddie/91a4d482be456ca75882df28589206f5 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
using System; | |
using Xamarin.Forms; | |
using System.Windows.Input; | |
using System.Collections.Generic; | |
using System.Runtime.CompilerServices; | |
using System.ComponentModel; | |
using System.Threading.Tasks; | |
[assembly: | |
InternalsVisibleTo ("TwinTechsLib.iOS"), | |
InternalsVisibleTo ("TwinTechsLib.Droid")] | |
namespace TwinTechs.Gestures | |
{ | |
public enum GestureRecognizerState | |
{ | |
Possible, | |
Began, | |
Changed, | |
Ended, | |
Cancelled, | |
Failed, | |
Recognized = 3 | |
} | |
//TODO I would love to make this generic! | |
/// <summary> | |
/// Base gesture recognizer. | |
/// </summary> | |
public class BaseGestureRecognizer : BindableObject, IGestureRecognizer | |
{ | |
/// <summary> | |
/// Delegate callback to check if this recognizer should proceed to begin state. | |
/// If false is returned, then the gesture will cancel. | |
/// If delayed touches is true, and false is returned, then the gesture will replay it's delayed touches | |
/// (implementaiton is slightly different on android/iOS; but the result should be more or less equal). | |
/// </summary> | |
/// <value>The view.</value> | |
public delegate bool GestureShouldBeginDelegate (BaseGestureRecognizer gestureRecognizer); | |
public GestureShouldBeginDelegate OnGestureShouldBeginDelegate; | |
public View View { get; set; } | |
/// <summary> | |
/// Gets or sets a value indicating whether this instance is consuming touches in parallel. | |
/// if true, then this gesture will register with the main touch dispatcher, and intercept touches as they occur at the system level | |
/// </summary> | |
/// <value><c>true</c> if this instance is consuming touches in parallel; otherwise, <c>false</c>.</value> | |
public bool IsConsumingTouchesInParallel { get; set; } | |
/// <summary> | |
/// Gets or sets the command. | |
/// </summary> | |
/// <value>The command.</value> | |
public ICommand Command { | |
get; | |
set; | |
} | |
/// <summary> | |
/// Gets or sets the command parameter. | |
/// </summary> | |
/// <value>The command parameter.</value> | |
public object CommandParameter { | |
get; | |
set; | |
} | |
/// <summary> | |
/// Gets or sets the OnAction callback. Made available in case your views need access to the gesture responses | |
/// </summary> | |
/// <value>The tapped callback.</value> | |
public event Action<BaseGestureRecognizer, GestureRecognizerState> OnAction; | |
bool _delaysTouches; | |
public bool DelaysTouches { | |
get{ return _delaysTouches; } | |
set { | |
_delaysTouches = value; | |
if (NativeGestureRecognizer != null) { | |
NativeGestureRecognizer.UpdateDelaysTouches (_delaysTouches); | |
} | |
} | |
} | |
bool _cancelsTouchesInView; | |
public bool CancelsTouchesInView { | |
get{ return _cancelsTouchesInView; } | |
set { | |
_cancelsTouchesInView = value; | |
if (NativeGestureRecognizer != null) { | |
NativeGestureRecognizer.UpdateCancelsTouchesInView (_cancelsTouchesInView); | |
} | |
} | |
} | |
public GestureRecognizerState State { get { return NativeGestureRecognizer == null ? GestureRecognizerState.Failed : NativeGestureRecognizer.State; } } | |
public int NumberOfTouches { get { return NativeGestureRecognizer == null ? 0 : NativeGestureRecognizer.NumberOfTouches; } } | |
public Point LocationInView (VisualElement view) | |
{ | |
return NativeGestureRecognizer.LocationInView (view); | |
} | |
public Point LocationOfTouch (int touchIndex, VisualElement view) | |
{ | |
return NativeGestureRecognizer.LocationOfTouch (touchIndex, view); | |
} | |
#region internal impl | |
internal void SendAction () | |
{ | |
if (Command != null) { | |
Command.Execute (CommandParameter); | |
} | |
if (OnAction != null) { | |
OnAction.Invoke (this, State); | |
} | |
} | |
/// <summary> | |
/// Sets the underlying gesture recognzier - used by the factory for adding/removal | |
/// </summary> | |
/// <value>The native gesture recognizer.</value> | |
internal INativeGestureRecognizer NativeGestureRecognizer { get; set; } | |
/// <summary> | |
/// Gets or sets the native gesture coordinator. - ONLY USED BY ANDROID | |
/// </summary> | |
/// <value>The native gesture coordinator.</value> | |
internal INativeGestureRecognizerCoordinator NativeGestureCoordinator { get; set; } | |
#endregion | |
public override string ToString () | |
{ | |
return string.Format ("[BaseGestureRecognizer: View={0}, State={1}]", View, State); | |
} | |
} | |
} | |
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; | |
using UIKit; | |
using Xamarin.Forms.Platform.iOS; | |
using Xamarin.Forms; | |
using TwinTechs.Ios.Extensions; | |
namespace TwinTechs.Gestures | |
{ | |
public interface IBaseNativeGestureRecognizerImpl : INativeGestureRecognizer | |
{ | |
void AddRecognizer (BaseGestureRecognizer recognizer); | |
void RemoveRecognizer (BaseGestureRecognizer recognizer); | |
} | |
public abstract class BaseNativeGestureRecognizer<NativeGestureType,T> : INativeGestureRecognizer,IBaseNativeGestureRecognizerImpl | |
where NativeGestureType : UIGestureRecognizer | |
where T : BaseGestureRecognizer | |
{ | |
protected T Recognizer { get; set; } | |
protected UIView NativeView { get; set; } | |
protected NativeGestureType NativeRecognizer { get; set; } | |
protected virtual void ConfigureNativeGestureRecognizer () | |
{ | |
NativeRecognizer.CancelsTouchesInView = Recognizer.CancelsTouchesInView; | |
NativeRecognizer.DelaysTouchesBegan = Recognizer.DelaysTouches; | |
// NativeRecognizer.DelaysTouchesEnded = Recognizer.DelaysTouchesEnded; | |
NativeRecognizer.ShouldRecognizeSimultaneously += _NativeRecognizer_ShouldRecognizeSimultaneously; | |
NativeRecognizer.ShouldBegin += _NativeRecognizer_ShouldBegin; | |
} | |
bool _NativeRecognizer_ShouldRecognizeSimultaneously (UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer) | |
{ | |
var renderer = Recognizer.View.GetRenderer (); | |
return renderer != null && Recognizer.IsConsumingTouchesInParallel; | |
} | |
bool _NativeRecognizer_ShouldBegin (UIGestureRecognizer recognizer) | |
{ | |
if (Recognizer.OnGestureShouldBeginDelegate != null) { | |
return Recognizer.OnGestureShouldBeginDelegate (Recognizer); | |
} else { | |
return true; | |
} | |
} | |
#region IBaseNativeGestureRecognizerImpl impl | |
public void AddRecognizer (BaseGestureRecognizer recognizer) | |
{ | |
Recognizer = (T)recognizer; | |
var renderer = Recognizer.View.GetRenderer (); | |
if (renderer == null) { | |
Recognizer.View.PropertyChanged += Recognizer_View_PropertyChanged; | |
} else { | |
InitializeNativeRecognizer (); | |
} | |
} | |
void Recognizer_View_PropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e) | |
{ | |
if (e.PropertyName == "Renderer") { | |
var renderer = Recognizer.View.GetRenderer (); | |
if (renderer != null && NativeView == null) { | |
InitializeNativeRecognizer (); | |
} else if (renderer == null && NativeView != null && NativeRecognizer != null) { | |
RemoveRecognizer (Recognizer); | |
} | |
} | |
} | |
void InitializeNativeRecognizer () | |
{ | |
var renderer = Recognizer.View.GetRenderer (); | |
if (renderer == null) { | |
throw new InvalidOperationException ("attempted to initialize a native gesture recognizers for a view before it had created it's renderer"); | |
} | |
NativeView = renderer.NativeView; | |
//workaroudn for irritating bugn which causes renderer to fail | |
if (typeof(NativeGestureType).Equals (typeof(UIPinchGestureRecognizer))) { | |
NativeRecognizer = new UIPinchGestureRecognizer (OnGesture) as NativeGestureType; | |
} else { | |
Action<NativeGestureType> action = OnGesture; | |
NativeRecognizer = (NativeGestureType)Activator.CreateInstance (typeof(NativeGestureType), action); | |
} | |
ConfigureNativeGestureRecognizer (); | |
NativeView.UserInteractionEnabled = true; | |
NativeView.AddGestureRecognizer (NativeRecognizer); | |
} | |
public void RemoveRecognizer (BaseGestureRecognizer recognizer) | |
{ | |
NativeView.RemoveGestureRecognizer (NativeRecognizer); | |
NativeRecognizer.ShouldRecognizeSimultaneously -= _NativeRecognizer_ShouldRecognizeSimultaneously; | |
NativeRecognizer.ShouldBegin -= _NativeRecognizer_ShouldBegin; | |
NativeRecognizer = null; | |
recognizer.NativeGestureRecognizer = null; | |
} | |
#endregion | |
#region IBaseNativeGestureRecognizer impl | |
public void UpdateCancelsTouchesInView (bool _cancelsTouchesInView) | |
{ | |
NativeRecognizer.CancelsTouchesInView = _cancelsTouchesInView; | |
} | |
public void UpdateDelaysTouches (bool _delaysTouches) | |
{ | |
NativeRecognizer.DelaysTouchesBegan = _delaysTouches; | |
} | |
public GestureRecognizerState State { | |
get { | |
return (GestureRecognizerState)(NativeRecognizer != null ? | |
GetGestureRecognizerStateFromUIState (NativeRecognizer.State) : GestureRecognizerState.Failed); | |
} | |
} | |
public Point LocationInView (VisualElement view) | |
{ | |
if (NativeRecognizer != null) { | |
var renderer = view.GetRenderer (); | |
return NativeRecognizer.LocationInView (renderer.NativeView).ToPoint (); | |
} else { | |
return Point.Zero; | |
} | |
} | |
public Point LocationOfTouch (int touchIndex, VisualElement view) | |
{ | |
if (NativeRecognizer != null) { | |
var renderer = view.GetRenderer (); | |
return NativeRecognizer.LocationOfTouch (touchIndex, renderer.NativeView).ToPoint (); | |
} else { | |
return Point.Zero; | |
} | |
} | |
public int NumberOfTouches { | |
get { | |
return (int)NativeRecognizer.NumberOfTouches; | |
} | |
} | |
GestureRecognizerState GetGestureRecognizerStateFromUIState (UIGestureRecognizerState state) | |
{ | |
switch (state) { | |
case UIGestureRecognizerState.Possible: | |
return GestureRecognizerState.Possible; | |
case UIGestureRecognizerState.Began: | |
return GestureRecognizerState.Began; | |
case UIGestureRecognizerState.Changed: | |
return GestureRecognizerState.Changed; | |
case UIGestureRecognizerState.Ended: | |
return GestureRecognizerState.Ended; | |
case UIGestureRecognizerState.Cancelled: | |
return GestureRecognizerState.Cancelled; | |
case UIGestureRecognizerState.Failed: | |
return GestureRecognizerState.Failed; | |
default: | |
throw new ArgumentOutOfRangeException (); | |
} | |
} | |
void OnGesture (UIGestureRecognizer recognizer) | |
{ | |
Recognizer.SendAction (); | |
} | |
#endregion | |
} | |
} | |
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
public GestureRecognizerState State { | |
get { | |
return _state; | |
} | |
protected set { | |
var oldState = _state; | |
_state = value; | |
if (oldState == GestureRecognizerState.Possible && value == GestureRecognizerState.Began) { | |
if (Recognizer.OnGestureShouldBeginDelegate != null && !Recognizer.OnGestureShouldBeginDelegate (Recognizer)) { | |
_state = GestureRecognizerState.Failed; | |
} | |
} | |
if (_state == GestureRecognizerState.Cancelled || _state == GestureRecognizerState.Ended || _state == GestureRecognizerState.Failed) { | |
PointerId = -1; | |
} | |
//we track if the gesture had begun at some point in processing this gesture, so we can elect which continuous events to send | |
if (_state == GestureRecognizerState.Began) { | |
_gestureDidBegin = true; | |
} | |
if (_state == GestureRecognizerState.Recognized || (IsGestureCotinuous && _gestureDidBegin)) { | |
SendGestureEvent (); | |
} | |
if (GetIsFinishedState (_state)) { | |
_gestureDidBegin = false; | |
} | |
} | |
} | |
bool GetIsFinishedState (GestureRecognizerState state) | |
{ | |
return state == GestureRecognizerState.Ended || state == GestureRecognizerState.Cancelled || state == GestureRecognizerState.Recognized || | |
state == GestureRecognizerState.Failed; | |
} |
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
var swipeGestureRecognizer = new SwipeGestureRecognizer(){ | |
Direction = SwipeGestureDirection.Left, | |
NumberOfTouchesRequired = 2 | |
}; | |
swipeGestureRecongizer.OnAction += (gesturRecgonizer) => Debug.WriteLine(“did a swipe”); | |
var myView.AddGestureRecognizer(swipeGestureRecognizer); |
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
_stackPanRecognizer = new PanGestureRecognizer (); | |
_stackPanRecognizer.OnAction += OnAction; | |
MyStack.AddGestureRecognizer (_stackPanRecognizer); |
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; | |
using Android.App; | |
using Android.Views; | |
using System.Collections.Generic; | |
namespace TwinTechs.Gestures | |
{ | |
/// <summary> | |
/// Helper class which provides a mechanism for android apps to dispatch touches to | |
/// multiple views which facilitates composing gesture recognition, | |
/// without having to have custom subclasses, while providing better orchestration of touches to gestures. | |
/// | |
/// The class hooks into an activity, which is expected to contain all of the view groups with views with touches | |
/// to coordinate | |
/// </summary> | |
public class GestureTouchDispatcher | |
{ | |
Activity _activity { get; set; } | |
Dictionary<MotionEvent,GestureMotionEvent> _delayedMotionEvents = new Dictionary<MotionEvent,GestureMotionEvent> (); | |
public GestureTouchDispatcher (Activity activity) | |
{ | |
_activity = activity; | |
} | |
public bool DispatchTouchEvent (MotionEvent ev) | |
{ | |
bool wasDelayed = _delayedMotionEvents.ContainsKey (ev); | |
if (wasDelayed) { | |
Console.WriteLine ("was delayed event - processing now " + ev); | |
var gestureEvent = _delayedMotionEvents [ev]; | |
_delayedMotionEvents.Remove (ev); | |
var restoredEvent = gestureEvent.GetCachedEvent (); | |
_activity.DispatchTouchEvent (restoredEvent); | |
var handled = _delayedMotionEvents.Remove (restoredEvent); | |
return handled; | |
} | |
var gestureMotionEvent = new GestureMotionEvent (ev); | |
//find if there's a view container with a gesture, which is currently on the screen. | |
foreach (var recognizer in NativeGestureCoordinator.GroupRecognizers) { | |
var nativeRecognizer = recognizer.NativeGestureRecognizer as BaseNativeGestureRecognizer; | |
// Console.WriteLine ("checkign gesture touch"); | |
nativeRecognizer.ProcessGestureMotionEvent (gestureMotionEvent); | |
gestureMotionEvent.IsConsumed = GetIsConsumedState (nativeRecognizer.State); | |
wasDelayed = wasDelayed || gestureMotionEvent.IsMarkedForDelay; | |
} | |
if (gestureMotionEvent.IsConsumed && gestureMotionEvent.IsCancelled) { | |
ev.Action = MotionEventActions.Cancel; | |
} | |
if (gestureMotionEvent.IsMarkedForDelay) { | |
_delayedMotionEvents [ev] = gestureMotionEvent; | |
} else if (wasDelayed) { | |
//it's been released from being delayed | |
_activity.DispatchTouchEvent (ev); | |
} | |
return gestureMotionEvent.IsConsumed; | |
} | |
bool GetIsConsumedState (GestureRecognizerState state) | |
{ | |
return state == GestureRecognizerState.Ended || state == GestureRecognizerState.Began || | |
state == GestureRecognizerState.Recognized || state == GestureRecognizerState.Changed; | |
} | |
} | |
} | |
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
<Label | |
Text="Tap me twice" | |
x:Name="Label1" | |
BackgroundColor="Olive" | |
HeightRequest="90" | |
TextColor="White" | |
FontSize="20"> | |
<Label.GestureRecognizers> | |
<gestures:TapGestureRecognizer | |
NumberOfTapsRequired="2" | |
OnAction="OnAction" /> | |
</Label.GestureRecognizers> | |
</Label> |
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
protected Xamarin.Forms.Point GetLocationInAncestorView (Xamarin.Forms.Point location, Xamarin.Forms.VisualElement view) | |
{ | |
int[] nativeViewLocation = new int[2]; | |
NativeView.GetLocationOnScreen (nativeViewLocation); | |
var nativeViewLocationOnScreen = new Xamarin.Forms.Point (nativeViewLocation [0], nativeViewLocation [1]); | |
var offsetLocation = new Xamarin.Forms.Point (location.X + nativeViewLocationOnScreen.X, location.Y + nativeViewLocationOnScreen.Y); | |
var targetViewRenderer = view.GetRenderer (); | |
var targetView = targetViewRenderer.ViewGroup; | |
int[] targetViewLocation = new int[2]; | |
targetView.GetLocationOnScreen (targetViewLocation); | |
var nativeViewScreenLocation = new Xamarin.Forms.Point (targetViewLocation [0], targetViewLocation [1]); | |
var returnPoint = offsetLocation; | |
returnPoint.X -= nativeViewScreenLocation.X; | |
returnPoint.Y -= nativeViewScreenLocation.Y; | |
// Console.WriteLine ("offsetLocation {0} nativeViewLocationOnScreen {1} returnPoint", offsetLocation, nativeViewLocationOnScreen); | |
// Console.WriteLine ("location {0} parentViewLoc {1} returnPoint {2}", location, nativeViewScreenLocation, returnPoint); | |
return returnPoint; | |
} |
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; | |
using System.Collections.Generic; | |
namespace TwinTechs.Gestures | |
{ | |
public class NativeGestureRecognizerFactory : INativeGestureRecognizerFactory | |
{ | |
public NativeGestureRecognizerFactory () | |
{ | |
} | |
Dictionary<Type,Type> TypeDictionary = new Dictionary<Type, Type> () { | |
{ typeof(TapGestureRecognizer) , typeof(NativeTapGestureRecognizer) }, | |
{ typeof(SwipeGestureRecognizer) , typeof(NativeSwipeGestureRecognizer) }, | |
{ typeof(PanGestureRecognizer) ,typeof(NativePanGestureRecognizer) }, | |
{ typeof(PinchGestureRecognizer), typeof(NativePinchGestureRecgonizer) }, | |
{ typeof(LongPressGestureRecognizer) , typeof(NativeLongPressGestureRecognizer) }, | |
}; | |
#region INativeGestureRecognizerFactory implementation | |
public void AddNativeGestureRecognizerToRecgonizer<T> (T recognizer) where T : BaseGestureRecognizer | |
{ | |
if (!TypeDictionary.ContainsKey (recognizer.GetType ())) { | |
throw new ArgumentException ("no native gesture recognizer for this forms recognizer " + recognizer.GetType ()); | |
} | |
var targetType = TypeDictionary [recognizer.GetType ()]; | |
var nativeRecongizer = (BaseNativeGestureRecognizer)Activator.CreateInstance (targetType); | |
nativeRecongizer.Recognizer = recognizer; | |
recognizer.NativeGestureRecognizer = nativeRecongizer; | |
if (recognizer.NativeGestureCoordinator == null) { | |
recognizer.NativeGestureCoordinator = new NativeGestureCoordinator (recognizer.View); | |
} | |
var coordinator = recognizer.NativeGestureCoordinator as NativeGestureCoordinator; | |
if (coordinator == null) { | |
throw new InvalidOperationException ("the recognizer's native gesture coordinator is null, or an invalid type"); | |
} | |
coordinator.AddRecognizer (nativeRecongizer); | |
} | |
public void RemoveRecognizer (BaseGestureRecognizer recognizer) | |
{ | |
if (recognizer.NativeGestureRecognizer != null) { | |
var coordinator = recognizer.NativeGestureCoordinator as NativeGestureCoordinator; | |
if (coordinator == null) { | |
throw new InvalidOperationException ("the recognizer's native gesture coordinator is null, or an invalid type"); | |
} | |
coordinator.RemoveRecognizer ((BaseNativeGestureRecognizer)recognizer.NativeGestureRecognizer); | |
if (!coordinator.HasRecognizers) { | |
coordinator.Destroy (); | |
recognizer.NativeGestureCoordinator = null; | |
} | |
} | |
} | |
#endregion | |
} | |
} | |
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; | |
using System.Collections.Generic; | |
namespace TwinTechs.Gestures | |
{ | |
/// <summary> | |
/// Creates gesture recognizers | |
/// </summary> | |
public class NativeGestureRecognizerFactory : INativeGestureRecognizerFactory | |
{ | |
#region INativeGestureRecognizerFactory implementation | |
Dictionary<Type,Type> TypeDictionary = new Dictionary<Type, Type> () { | |
{ typeof(SwipeGestureRecognizer) , typeof(NativeSwipeGestureRecognizer) }, | |
{ typeof(PanGestureRecognizer) , typeof(NativePanGestureRecognizer) }, | |
{ typeof(PinchGestureRecognizer) , typeof(NativePinchGestureRecognizer) }, | |
{ typeof(LongPressGestureRecognizer) , typeof(NativeLongPressGestureRecgonizer) }, | |
{ typeof(TwinTechs.Gestures.TapGestureRecognizer) , typeof(NativeTapPressGestureRecgonizer) }, | |
}; | |
public void AddNativeGestureRecognizerToRecgonizer<T> (T recognizer) where T : BaseGestureRecognizer | |
{ | |
if (!TypeDictionary.ContainsKey (recognizer.GetType ())) { | |
throw new ArgumentException ("no native gesture recognizer for this forms recognizer " + recognizer.GetType ()); | |
} | |
var targetType = TypeDictionary [recognizer.GetType ()]; | |
var nativeRecongizer = (IBaseNativeGestureRecognizerImpl)Activator.CreateInstance (targetType); | |
nativeRecongizer.AddRecognizer (recognizer); | |
recognizer.NativeGestureRecognizer = nativeRecongizer; | |
} | |
public INativeGestureRecognizerCoordinator CreateNativeGestureCoordinator () | |
{ | |
throw new InvalidOperationException ("iOS does not use the native gesture coordinator."); | |
} | |
public void RemoveRecognizer (BaseGestureRecognizer recognizer) | |
{ | |
var nativeRecognizer = recognizer.NativeGestureRecognizer as IBaseNativeGestureRecognizerImpl; | |
nativeRecognizer.RemoveRecognizer (recognizer); | |
} | |
#endregion | |
} | |
} | |
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; | |
using Android.Views; | |
using Android.Graphics; | |
using TwinTechs.Droid.Extensions; | |
using Xamarin.Forms; | |
namespace TwinTechs.Gestures | |
{ | |
public class NativeLongPressGestureRecognizer : BaseNativeGestureRecognizer | |
{ | |
public NativeLongPressGestureRecognizer () | |
{ | |
} | |
LongPressGestureRecognizer LongPressGestureRecognizer { get { return Recognizer as LongPressGestureRecognizer; } } | |
System.Timers.Timer _longPressTimer; | |
#region implemented abstract members of BaseNativeGestureRecognizer | |
internal override void ProcessMotionEvent (GestureMotionEvent e) | |
{ | |
if (e.ActionMasked == MotionEventActions.Down && PointerId == -1) { | |
OnDown (e); | |
// e.IsConsumed = true; | |
e.IsCancelled = Recognizer.CancelsTouchesInView; | |
} else if (State == GestureRecognizerState.Cancelled || State == GestureRecognizerState.Ended || State == GestureRecognizerState.Failed) { | |
return; | |
} else { | |
var xMovement = Math.Abs (e.GetX (0) - FirstTouchPoint.X); | |
var yMovement = Math.Abs (e.GetY (0) - FirstTouchPoint.Y); | |
var isMovedBeyondMaxDistance = xMovement > LongPressGestureRecognizer.MaxDistanceTolerance || yMovement > LongPressGestureRecognizer.MaxDistanceTolerance; | |
Console.WriteLine ("isMovedBeyondMaxDistance {0} xd {1} yd{2}", isMovedBeyondMaxDistance, xMovement, yMovement); | |
if (e.ActionMasked == MotionEventActions.Cancel || isMovedBeyondMaxDistance) { | |
State = GestureRecognizerState.Cancelled; | |
Console.WriteLine ("LONG PRESS CANCELLED"); | |
} else if (e.ActionMasked == MotionEventActions.Up) { | |
OnUp (e); | |
// e.IsConsumed = true; | |
} | |
} | |
} | |
#endregion | |
void OnDown (GestureMotionEvent e) | |
{ | |
//TODO - should really be possible until all taps/fingers are satisfied. | |
if (e.PointerCount == LongPressGestureRecognizer.NumberOfTouchesRequired) { | |
State = GestureRecognizerState.Began; | |
PointerId = e.GetPointerId (0); | |
FirstTouchPoint = new Xamarin.Forms.Point (e.GetX (0), e.GetY (0)); | |
ResetLongPressTimer (true); | |
} else { | |
State = GestureRecognizerState.Failed; | |
} | |
} | |
void OnUp (GestureMotionEvent e) | |
{ | |
ResetLongPressTimer (false); | |
//TODO track the correct fingers | |
if (State == GestureRecognizerState.Began) { | |
State = GestureRecognizerState.Failed; | |
Console.WriteLine ("LONG PRESS CANCELLED"); | |
} | |
} | |
void _longPressTapTimer_Elapsed (object sender, System.Timers.ElapsedEventArgs e) | |
{ | |
Console.WriteLine ("LONG PRESS RECOGNIZED"); | |
ResetLongPressTimer (false); | |
State = GestureRecognizerState.Recognized; | |
} | |
void ResetLongPressTimer (bool isActive) | |
{ | |
if (_longPressTimer != null) { | |
_longPressTimer.Elapsed -= _longPressTapTimer_Elapsed; | |
_longPressTimer.Stop (); | |
} | |
if (isActive) { | |
State = GestureRecognizerState.Possible; | |
_longPressTimer = new System.Timers.Timer (); | |
_longPressTimer.AutoReset = false; | |
_longPressTimer.Interval = LongPressGestureRecognizer.MinimumPressDuration * 1000; | |
_longPressTimer.Elapsed += _longPressTapTimer_Elapsed; | |
_longPressTimer.Start (); | |
} | |
} | |
} | |
} | |
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; | |
using UIKit; | |
using System.Drawing; | |
using Xamarin.Forms; | |
using Xamarin.Forms.Platform.iOS; | |
using TwinTechs.Ios.Extensions; | |
using CoreGraphics; | |
namespace TwinTechs.Gestures | |
{ | |
public class NativePanGestureRecognizer : BaseNativeGestureRecognizer<UIPanGestureRecognizer,PanGestureRecognizer> | |
, INativePanGestureRecognizer | |
{ | |
public NativePanGestureRecognizer () | |
{ | |
} | |
#region overridden | |
protected override void ConfigureNativeGestureRecognizer () | |
{ | |
base.ConfigureNativeGestureRecognizer (); | |
NativeRecognizer.MinimumNumberOfTouches = (nuint)Recognizer.MinimumNumberOfTouches; | |
NativeRecognizer.MaximumNumberOfTouches = (nuint)Recognizer.MaximumNumberOfTouches; | |
} | |
#endregion | |
#region INativePanGestureRecognizer impl | |
public Xamarin.Forms.Point GetVelocityInView (VisualElement view) | |
{ | |
var renderer = view.GetRenderer (); | |
return NativeRecognizer.VelocityInView (renderer.NativeView).ToPoint (); | |
} | |
public Xamarin.Forms.Point GetTranslationInView (VisualElement view) | |
{ | |
var renderer = view.GetRenderer (); | |
if (renderer == null || renderer.NativeView == null) { | |
//TODO -not sure why this isn't working on iOS. very weird. | |
return new Xamarin.Forms.Point (0, 0); | |
} | |
return NativeRecognizer.TranslationInView (renderer.NativeView).ToPoint (); | |
} | |
public void SetTranslationInView (Xamarin.Forms.Point translation, VisualElement view) | |
{ | |
var renderer = view.GetRenderer (); | |
NativeRecognizer.SetTranslation (new CGPoint (translation.X, translation.Y), renderer.NativeView); | |
} | |
#endregion | |
} | |
} | |
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; | |
using Xamarin.Forms; | |
using Xamarin.Forms.Platform.iOS; | |
using UIKit; | |
using CoreImage; | |
using Foundation; | |
namespace TwinTechs.Gestures | |
{ | |
public class NativeSwipeGestureRecognizer : BaseNativeGestureRecognizer<UISwipeGestureRecognizer,SwipeGestureRecognizer> | |
{ | |
public NativeSwipeGestureRecognizer () | |
{ | |
} | |
#region abstract impl | |
protected override void ConfigureNativeGestureRecognizer () | |
{ | |
base.ConfigureNativeGestureRecognizer (); | |
NativeRecognizer.Direction = (UISwipeGestureRecognizerDirection)Recognizer.Direction; | |
NativeRecognizer.NumberOfTouchesRequired = (nuint)Recognizer.NumberOfTouchesRequired; | |
} | |
#endregion | |
} | |
} | |
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; | |
using Android.Views; | |
using Xamarin.Forms; | |
namespace TwinTechs.Gestures | |
{ | |
public class NativeSwipeGestureRecognizer : BaseNativeGestureRecognizer | |
{ | |
const int MinimumSwipeDistance = 5; | |
const int MaxSwipeDuration = 1000; | |
DateTime _startTime; | |
public NativeSwipeGestureRecognizer () | |
{ | |
} | |
SwipeGestureRecognizer SwipeGestureRecognizer { get { return Recognizer as SwipeGestureRecognizer; } } | |
#region implemented abstract members of BaseNativeGestureRecognizer | |
internal override void ProcessMotionEvent (GestureMotionEvent e) | |
{ | |
if (e.Action == MotionEventActions.Down && PointerId == -1) { | |
OnDown (e); | |
//TODO - this should probably be possible at this point? | |
if (State == GestureRecognizerState.Began) { | |
//TODO track all pointers that are down. | |
PointerId = e.GetPointerId (0); | |
// e.IsConsumed = true; | |
e.IsCancelled = Recognizer.CancelsTouchesInView; | |
} | |
} else if (State == GestureRecognizerState.Cancelled || State == GestureRecognizerState.Ended || State == GestureRecognizerState.Failed) { | |
return; | |
} else if (e.ActionMasked == MotionEventActions.Cancel) { | |
State = GestureRecognizerState.Cancelled; | |
Console.WriteLine ("GESTURE CANCELLED"); | |
} else if (e.ActionMasked == MotionEventActions.Up) { | |
OnUp (e); | |
// e.IsConsumed = State != GestureRecognizerState.Failed; | |
} | |
} | |
#endregion | |
void OnDown (GestureMotionEvent e) | |
{ | |
//TODO - should really be possible until all taps/fingers are satisfied. | |
State = GestureRecognizerState.Began; | |
// State = (e.PointerCount == SwipeGestureRecognizer.NumberOfTouchesRequired) ? GestureRecognizerState.Began : GestureRecognizerState.Failed; | |
FirstTouchPoint = new Xamarin.Forms.Point (e.GetX (0), e.GetY (0)); | |
_startTime = DateTime.Now; | |
} | |
void OnUp (GestureMotionEvent e) | |
{ | |
NumberOfTouches = e.PointerCount; | |
var tookTooLong = (DateTime.Now - _startTime).Milliseconds > MaxSwipeDuration; | |
var wrongAmountOfTouches = NumberOfTouches < SwipeGestureRecognizer.NumberOfTouchesRequired; | |
if (tookTooLong || wrongAmountOfTouches) { | |
State = GestureRecognizerState.Failed; | |
return; | |
} | |
var endTouchPoint = new Xamarin.Forms.Point (e.GetX (0), e.GetY (0)); | |
double velocityX = endTouchPoint.X - FirstTouchPoint.X; | |
double velocityY = endTouchPoint.Y - FirstTouchPoint.Y; | |
var direction = GetSwipeDirection (velocityX, velocityY); | |
var expectedDirection = (Recognizer as SwipeGestureRecognizer).Direction; | |
if (direction == expectedDirection) { | |
State = GestureRecognizerState.Recognized; | |
} else { | |
State = GestureRecognizerState.Failed; | |
Console.WriteLine ("failed gesture was expecting {0} got {1}", expectedDirection, direction); | |
} | |
} | |
SwipeGestureRecognizerDirection GetSwipeDirection (double velocityX, double velocityY) | |
{ | |
var isHorizontalSwipe = Math.Abs (velocityX) > Math.Abs (velocityY); | |
if (isHorizontalSwipe) { | |
return velocityX > 0 ? SwipeGestureRecognizerDirection.Right : SwipeGestureRecognizerDirection.Left; | |
} else { | |
return velocityY > 0 ? SwipeGestureRecognizerDirection.Down : SwipeGestureRecognizerDirection.Up; | |
} | |
} | |
} | |
} | |
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; | |
using Xamarin.Forms; | |
namespace TwinTechs.Gestures | |
{ | |
public interface INativePanGestureRecognizer : INativeGestureRecognizer | |
{ | |
Point GetVelocityInView (VisualElement view); | |
void SetTranslationInView (Point translation, VisualElement view); | |
Point GetTranslationInView (VisualElement view); | |
} | |
public class PanGestureRecognizer : BaseGestureRecognizer | |
{ | |
public int MinimumNumberOfTouches { get; set; } | |
public int MaximumNumberOfTouches { get; set; } | |
public Point GetVelocityInView (VisualElement view) | |
{ | |
return (NativeGestureRecognizer as INativePanGestureRecognizer).GetVelocityInView (view); | |
} | |
public void SetTranslationInView (Point translation, VisualElement view) | |
{ | |
(NativeGestureRecognizer as INativePanGestureRecognizer).SetTranslationInView (translation, view); | |
} | |
public Point GetTranslationInView (VisualElement view) | |
{ | |
return (NativeGestureRecognizer as INativePanGestureRecognizer).GetTranslationInView (view); | |
} | |
public PanGestureRecognizer () | |
{ | |
MinimumNumberOfTouches = 1; | |
MaximumNumberOfTouches = 1; | |
} | |
public override string ToString () | |
{ | |
return string.Format ("[PanGestureRecognizer: MinimumNumberOfTouches={0}, MaximumNumberOfTouches={1}, State={2}]", MinimumNumberOfTouches, MaximumNumberOfTouches, State); | |
} | |
} | |
} | |
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
/** | |
* We have an issue with Android that makes it pretty much impossible to compose gestures. | |
* Below is my stab at picking up gestures from the activity - the idea is that the main activity would pass all touches to this method | |
* the system will work, and will work very richly (with delay touch, cancel touches, delegate callback for should | |
* recognize in parallel, etc) | |
* However, my time is limited, so this is crude for now. | |
*/ | |
public void ProcessGestureMotionEvent (GestureMotionEvent gestureEvent) | |
{ | |
var ev = gestureEvent.MotionEvent; | |
var nativeViewScreenLocation = Recognizer.View.GetNativeScreenPosition (); | |
var offset = Xamarin.Forms.Point.Zero; | |
var touchPoint = new Xamarin.Forms.Point (ev.GetX (), ev.GetY ()); | |
var mainPointerId = ev.GetPointerId (0); | |
//1. is it inside the view? | |
// Console.WriteLine ("touch point {0} vlocs {1} vlocw {2}", touchPoint.PrettyPrint (), nativeViewScreenLocation.PrettyPrint (), nativeViewWindowLocation.PrettyPrint ()); | |
// Console.WriteLine ("touch point {0} view bounds {1} size {2},{3}", touchPoint, nativeViewScreenLocation, NativeView.Width, NativeView.Height); | |
var isInsideOfView = touchPoint.X >= nativeViewScreenLocation.X && touchPoint.Y >= nativeViewScreenLocation.Y && | |
touchPoint.X <= (NativeView.Width + nativeViewScreenLocation.X) && touchPoint.Y <= (NativeView.Height + nativeViewScreenLocation.Y); | |
//2. report touches inside, or outside but tracked? (so cancels can occur) | |
//TODO track more touches | |
if (isInsideOfView || PointerId == mainPointerId) { | |
//if letting the view know, translate the coords into local view coords (apply the offsets to the touch) | |
offset.X = -nativeViewScreenLocation.X; | |
offset.Y = -nativeViewScreenLocation.Y; | |
ev.OffsetLocation ((float)offset.X, (float)offset.Y); | |
var offsetLocation = new Xamarin.Forms.Point (ev.GetX (), ev.GetY ()); | |
if (isInsideOfView) { | |
// Console.WriteLine ("INSIDE " + ev.Action + " offset " + offset.PrettyPrint () + " results in " + offsetLocation.PrettyPrint ()); | |
} else { | |
// Console.WriteLine ("touch outside view, but was tracked " + offset); | |
} | |
//TODO - ask the view if it's happy to process this touch at the same time as another gesture - I see no way to make it work for views.. (without | |
//an entire Touch dispatching mechanism:/) | |
//that will be done by 2 parses - one to discover all *gestures* that want the touch, then another parse to go back through and either cancel | |
//or pass the touches long | |
//that's not implemented yet though (time) | |
ProcessMotionEvent (gestureEvent); | |
//remove the offset | |
ev.OffsetLocation ((float)-offset.X, (float)-offset.Y); | |
} | |
// Console.WriteLine ("location " + ev.GetX () + ", " + ev.GetY () + " offset " + offset); | |
} |
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; | |
using Xamarin.Forms; | |
namespace TwinTechs.Gestures | |
{ | |
public interface ITapGestureRecognizer : INativeGestureRecognizer | |
{ | |
} | |
public class TapGestureRecognizer : BaseGestureRecognizer | |
{ | |
public TapGestureRecognizer () | |
{ | |
NumberOfTapsRequired = 1; | |
NumberOfTouchesRequired = 1; | |
} | |
public int NumberOfTapsRequired { get; set; } | |
public int NumberOfTouchesRequired { get; set; } | |
public override string ToString () | |
{ | |
return string.Format ("[TapGestureRecognizer: NumberOfTapsRequired={0}, NumberOfTouchesRequired={1}, State={2}]", NumberOfTapsRequired, NumberOfTouchesRequired, State); | |
} | |
} | |
} | |
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
<?xml version="1.0" encoding="UTF-8"?> | |
<ContentPage | |
xmlns="http://xamarin.com/schemas/2014/forms" | |
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
x:Class="TwinTechs.Example.Gestures.GestureYoutubeLikeExample" | |
xmlns:gestures="clr-namespace:TwinTechs.Gestures;assembly=TwinTechsForms" | |
xmlns:cells="clr-namespace:TwinTechs.Example.Gestures.Cells;assembly=TwinTechsFormsExample" | |
xmlns:local="clr-namespace:TwinTechs.Example.Gestures;assembly=TwinTechsFormsExample" | |
xmlns:controls="clr-namespace:TwinTechs.Controls;assembly=TwinTechsForms" | |
BackgroundColor="Silver" | |
Title="SwipeExample"> | |
<local:SimpleLayout | |
BackgroundColor="Silver" | |
x:Name="MainLayout" | |
IsHandlingLayoutManually="true"> | |
<ListView | |
BackgroundColor="Silver" | |
x:Name="MediaItemsListView" | |
SeparatorVisibility="None" | |
ItemSelected="OnItemSelected" | |
RowHeight="100"> | |
<ListView.ItemTemplate> | |
<DataTemplate> | |
<cells:VideoCell /> | |
</DataTemplate> | |
</ListView.ItemTemplate> | |
</ListView> | |
<controls:PageViewContainer | |
BackgroundColor="Transparent" | |
x:Name="PageContainer" /> | |
</local:SimpleLayout> | |
</ContentPage> |
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; | |
using System.Collections.Generic; | |
using Xamarin.Forms; | |
using TwinTechs.Gestures; | |
using System.Diagnostics; | |
namespace TwinTechs.Example.Gestures | |
{ | |
public partial class GestureYoutubeLikeExample : ContentPage | |
{ | |
Rectangle _contentBounds = new Rectangle (100, 200, 150, 150); | |
YoutubeStyleContentPage _contentPage; | |
PanGestureRecognizer _panGesture; | |
bool _didLayoutContainer; | |
public GestureYoutubeLikeExample () | |
{ | |
InitializeComponent (); | |
MainLayout.OnLayoutChildren += MainLayout_OnLayoutChildren; | |
MediaItemsListView.ItemsSource = DataProvider.GetMediaItems (); | |
} | |
protected override void LayoutChildren (double x, double y, double width, double height) | |
{ | |
base.LayoutChildren (x, y, width, height); | |
if (_contentPage != null) { | |
_contentPage.ParentHeight = height; | |
} | |
} | |
void MainLayout_OnLayoutChildren (double x, double y, double width, double height) | |
{ | |
MediaItemsListView.Layout (new Rectangle (0, 0, Width, Height)); | |
if (!_didLayoutContainer) { | |
_contentBounds.Y = height - 100; | |
_contentBounds.X = width - 160; | |
_contentBounds.Width = 160; | |
_contentBounds.Height = 100; | |
_didLayoutContainer = true; | |
} | |
PageContainer.Layout (_contentBounds); | |
} | |
Rectangle _startBounds; | |
void Gesture_OnAction (BaseGestureRecognizer recgonizer, GestureRecognizerState state) | |
{ | |
if (recgonizer.View != _contentPage.VideoPlayerView) { | |
return; | |
} | |
var panGesture = recgonizer as PanGestureRecognizer; | |
Point translation = panGesture.GetTranslationInView (MainLayout); | |
Point velocity = panGesture.GetVelocityInView (MainLayout); | |
panGesture.SetTranslationInView (new Point (0, 0), MainLayout); | |
switch (panGesture.State) { | |
case GestureRecognizerState.Began: | |
break; | |
case GestureRecognizerState.Changed: | |
var newY = _contentBounds.Y + translation.Y; | |
if (newY > 0 && newY < Height - _contentPage.MinimumHeightRequest) { | |
var minHeight = _contentPage.MinimumHeightRequest; | |
var minWidth = _contentPage.MinimumWidthRequest; | |
_contentBounds.Y = newY; | |
var complete = Math.Min (1, (Height - (_contentBounds.Y + minHeight)) / Height); | |
// Debug.WriteLine ("complete {0} newY {1} h{2}", complete, newY, Height); | |
var inverseCompletion = 1 - complete; | |
_contentBounds.X = (Width - minWidth) * inverseCompletion; | |
_contentBounds.Width = (minWidth) + ((Width - minWidth) * complete); | |
_contentBounds.Height = Math.Max (minHeight, (Height + minHeight) * complete); | |
PageContainer.Layout (_contentBounds); | |
} | |
break; | |
case GestureRecognizerState.Cancelled: | |
case GestureRecognizerState.Ended: | |
case GestureRecognizerState.Failed: | |
var isShowing = _contentBounds.Y < 200; | |
ToggleShowing (isShowing, true); | |
break; | |
default: | |
break; | |
} | |
} | |
protected override void OnDisappearing () | |
{ | |
base.OnDisappearing (); | |
if (_contentPage != null) { | |
_contentPage.VideoPlayerView.RemoveAllGestureRecognizers (); | |
} | |
} | |
void OnItemSelected (object sender, SelectedItemChangedEventArgs e) | |
{ | |
ToggleShowing (true, true); | |
} | |
void ToggleShowing (bool isShowing, bool animated) | |
{ | |
if (_contentPage == null) { | |
_contentPage = new YoutubeStyleContentPage (); | |
_contentPage.ParentHeight = Height; | |
PageContainer.Content = _contentPage; | |
_panGesture = new PanGestureRecognizer (); | |
_panGesture.OnAction += Gesture_OnAction; | |
_panGesture.IsConsumingTouchesInParallel = true; | |
_contentPage.VideoPlayerView.AddGestureRecognizer (_panGesture); | |
} | |
var minHeight = _contentPage.MinimumHeightRequest; | |
var minWidth = _contentPage.MinimumWidthRequest; | |
_contentBounds.Y = isShowing ? 0 : Height - minHeight; | |
_contentBounds.X = isShowing ? 0 : Width - minWidth; | |
_contentBounds.Width = isShowing ? Width : minWidth; | |
_contentBounds.Height = isShowing ? Height : minHeight; | |
if (MediaItemsListView.SelectedItem != null) { | |
_contentPage.Item = MediaItemsListView.SelectedItem as MediaItem; | |
} | |
if (animated) { | |
PageContainer.LayoutTo (_contentBounds); | |
} else { | |
PageContainer.Layout (_contentBounds); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment