Xamarin.Forms的View没有touch事件,只能自己实现

首先,在共享项目里面,放入这几个类,结构大概是这样的:

Xamarin.Forms实现touch事件-LMLPHP

using System;
using Xamarin.Forms; namespace TouchTracking
{
public class TouchActionEventArgs : EventArgs
{
public TouchActionEventArgs(long id, TouchActionType type, Point location, bool isInContact)
{
Id = id;
Type = type;
Location = location;
IsInContact = isInContact;
} public long Id { private set; get; } public TouchActionType Type { private set; get; } public Point Location { private set; get; } public bool IsInContact { private set; get; }
}
}
namespace TouchTracking
{
public delegate void TouchActionEventHandler(object sender, TouchActionEventArgs args);
}
namespace TouchTracking
{
public enum TouchActionType
{
Entered,
Pressed,
Moved,
Released,
Exited,
Cancelled
}
}
using Xamarin.Forms;

namespace TouchTracking
{
public class TouchEffect : RoutingEffect
{
public event TouchActionEventHandler TouchAction; public TouchEffect() : base("XamarinDocs.TouchEffect")
{
} public bool Capture { set; get; } public void OnTouchAction(Element element, TouchActionEventArgs args)
{
TouchAction?.Invoke(element, args);
}
}
}
using System;
using SkiaSharp;
using TouchTracking; namespace SkiaSharpFormsDemos
{
public class TouchPoint
{
// For painting
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Fill
}; // For dragging
bool isBeingDragged;
long touchId;
SKPoint previousPoint; public TouchPoint()
{
} public TouchPoint(float x, float y)
{
Center = new SKPoint(x, y);
} public SKPoint Center { set; get; } public float Radius { set; get; } = ; public SKColor Color { set; get; } = new SKColor(, , , ); public void Paint(SKCanvas canvas)
{
paint.Color = Color;
canvas.DrawCircle(Center.X, Center.Y, Radius, paint);
} public bool ProcessTouchEvent(long id, TouchActionType type, SKPoint location)
{
bool centerMoved = false; // Assumes Capture property of TouchEffect is true!
switch (type)
{
case TouchActionType.Pressed:
if (!isBeingDragged && PointInCircle(location))
{
isBeingDragged = true;
touchId = id;
previousPoint = location;
centerMoved = false;
}
break; case TouchActionType.Moved:
if (isBeingDragged && touchId == id)
{
Center += location - previousPoint;
previousPoint = location;
centerMoved = true;
}
break; case TouchActionType.Released:
if (isBeingDragged && touchId == id)
{
Center += location - previousPoint;
isBeingDragged = false;
centerMoved = true;
}
break; case TouchActionType.Cancelled:
isBeingDragged = false;
break;
}
return centerMoved;
} bool PointInCircle(SKPoint pt)
{
return (Math.Pow(pt.X - Center.X, ) + Math.Pow(pt.Y - Center.Y, )) < (Radius * Radius);
}
}
}

然后,在android的项目里面,加入这个类

using System;
using System.Collections.Generic;
using System.Linq; using Xamarin.Forms;
using Xamarin.Forms.Platform.Android; using Android.Views; [assembly: ResolutionGroupName("XamarinDocs")]
[assembly: ExportEffect(typeof(TouchTracking.Droid.TouchEffect), "TouchEffect")] namespace TouchTracking.Droid
{
public class TouchEffect : PlatformEffect
{
Android.Views.View view;
Element formsElement;
TouchTracking.TouchEffect libTouchEffect;
bool capture;
Func<double, double> fromPixels;
int[] twoIntArray = new int[]; static Dictionary<Android.Views.View, TouchEffect> viewDictionary =
new Dictionary<Android.Views.View, TouchEffect>(); static Dictionary<int, TouchEffect> idToEffectDictionary =
new Dictionary<int, TouchEffect>(); protected override void OnAttached()
{
// Get the Android View corresponding to the Element that the effect is attached to
view = Control == null ? Container : Control; // Get access to the TouchEffect class in the .NET Standard library
TouchTracking.TouchEffect touchEffect =
(TouchTracking.TouchEffect)Element.Effects.
FirstOrDefault(e => e is TouchTracking.TouchEffect); if (touchEffect != null && view != null)
{
viewDictionary.Add(view, this); formsElement = Element; libTouchEffect = touchEffect; // Save fromPixels function
fromPixels = view.Context.FromPixels; // Set event handler on View
view.Touch += OnTouch;
}
} protected override void OnDetached()
{
if (viewDictionary.ContainsKey(view))
{
viewDictionary.Remove(view);
view.Touch -= OnTouch;
}
} void OnTouch(object sender, Android.Views.View.TouchEventArgs args)
{
// Two object common to all the events
Android.Views.View senderView = sender as Android.Views.View;
MotionEvent motionEvent = args.Event; // Get the pointer index
int pointerIndex = motionEvent.ActionIndex; // Get the id that identifies a finger over the course of its progress
int id = motionEvent.GetPointerId(pointerIndex); senderView.GetLocationOnScreen(twoIntArray);
Point screenPointerCoords = new Point(twoIntArray[] + motionEvent.GetX(pointerIndex),
twoIntArray[] + motionEvent.GetY(pointerIndex)); // Use ActionMasked here rather than Action to reduce the number of possibilities
switch (args.Event.ActionMasked)
{
case MotionEventActions.Down:
case MotionEventActions.PointerDown:
FireEvent(this, id, TouchActionType.Pressed, screenPointerCoords, true); idToEffectDictionary.Add(id, this); capture = libTouchEffect.Capture;
break; case MotionEventActions.Move:
// Multiple Move events are bundled, so handle them in a loop
for (pointerIndex = ; pointerIndex < motionEvent.PointerCount; pointerIndex++)
{
id = motionEvent.GetPointerId(pointerIndex); if (capture)
{
senderView.GetLocationOnScreen(twoIntArray); screenPointerCoords = new Point(twoIntArray[] + motionEvent.GetX(pointerIndex),
twoIntArray[] + motionEvent.GetY(pointerIndex)); FireEvent(this, id, TouchActionType.Moved, screenPointerCoords, true);
}
else
{
CheckForBoundaryHop(id, screenPointerCoords); if (idToEffectDictionary[id] != null)
{
FireEvent(idToEffectDictionary[id], id, TouchActionType.Moved, screenPointerCoords, true);
}
}
}
break; case MotionEventActions.Up:
case MotionEventActions.Pointer1Up:
if (capture)
{
FireEvent(this, id, TouchActionType.Released, screenPointerCoords, false);
}
else
{
CheckForBoundaryHop(id, screenPointerCoords); if (idToEffectDictionary[id] != null)
{
FireEvent(idToEffectDictionary[id], id, TouchActionType.Released, screenPointerCoords, false);
}
}
idToEffectDictionary.Remove(id);
break; case MotionEventActions.Cancel:
if (capture)
{
FireEvent(this, id, TouchActionType.Cancelled, screenPointerCoords, false);
}
else
{
if (idToEffectDictionary[id] != null)
{
FireEvent(idToEffectDictionary[id], id, TouchActionType.Cancelled, screenPointerCoords, false);
}
}
idToEffectDictionary.Remove(id);
break;
}
} void CheckForBoundaryHop(int id, Point pointerLocation)
{
TouchEffect touchEffectHit = null; foreach (Android.Views.View view in viewDictionary.Keys)
{
// Get the view rectangle
try
{
view.GetLocationOnScreen(twoIntArray);
}
catch // System.ObjectDisposedException: Cannot access a disposed object.
{
continue;
}
Rectangle viewRect = new Rectangle(twoIntArray[], twoIntArray[], view.Width, view.Height); if (viewRect.Contains(pointerLocation))
{
touchEffectHit = viewDictionary[view];
}
} if (touchEffectHit != idToEffectDictionary[id])
{
if (idToEffectDictionary[id] != null)
{
FireEvent(idToEffectDictionary[id], id, TouchActionType.Exited, pointerLocation, true);
}
if (touchEffectHit != null)
{
FireEvent(touchEffectHit, id, TouchActionType.Entered, pointerLocation, true);
}
idToEffectDictionary[id] = touchEffectHit;
}
} void FireEvent(TouchEffect touchEffect, int id, TouchActionType actionType, Point pointerLocation, bool isInContact)
{
// Get the method to call for firing events
Action<Element, TouchActionEventArgs> onTouchAction = touchEffect.libTouchEffect.OnTouchAction; // Get the location of the pointer within the view
touchEffect.view.GetLocationOnScreen(twoIntArray);
double x = pointerLocation.X - twoIntArray[];
double y = pointerLocation.Y - twoIntArray[];
Point point = new Point(fromPixels(x), fromPixels(y)); // Call the method
onTouchAction(touchEffect.formsElement,
new TouchActionEventArgs(id, actionType, point, isInContact));
}
}
}

然后,在ios的项目里面,加入这个类

using System;
using System.Linq; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using System.Collections.Generic; using CoreGraphics;
using Foundation;
using UIKit; [assembly: ResolutionGroupName("XamarinDocs")]
[assembly: ExportEffect(typeof(TouchTracking.iOS.TouchEffect), "TouchEffect")] namespace TouchTracking.iOS
{
public class TouchEffect : PlatformEffect
{
UIView view;
TouchRecognizer touchRecognizer; protected override void OnAttached()
{
// Get the iOS UIView corresponding to the Element that the effect is attached to
view = Control == null ? Container : Control; // Get access to the TouchEffect class in the .NET Standard library
TouchTracking.TouchEffect effect = (TouchTracking.TouchEffect)Element.Effects.FirstOrDefault(e => e is TouchTracking.TouchEffect); if (effect != null && view != null)
{
// Create a TouchRecognizer for this UIView
touchRecognizer = new TouchRecognizer(Element, view, effect);
view.AddGestureRecognizer(touchRecognizer);
}
} protected override void OnDetached()
{
if (touchRecognizer != null)
{
// Clean up the TouchRecognizer object
touchRecognizer.Detach(); // Remove the TouchRecognizer from the UIView
view.RemoveGestureRecognizer(touchRecognizer);
}
}
} class TouchRecognizer : UIGestureRecognizer
{
Element element; // Forms element for firing events
UIView view; // iOS UIView
TouchTracking.TouchEffect touchEffect;
bool capture; static Dictionary<UIView, TouchRecognizer> viewDictionary =
new Dictionary<UIView, TouchRecognizer>(); static Dictionary<long, TouchRecognizer> idToTouchDictionary =
new Dictionary<long, TouchRecognizer>(); public TouchRecognizer(Element element, UIView view, TouchTracking.TouchEffect touchEffect)
{
this.element = element;
this.view = view;
this.touchEffect = touchEffect; viewDictionary.Add(view, this);
} public void Detach()
{
viewDictionary.Remove(view);
} // touches = touches of interest; evt = all touches of type UITouch
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt); foreach (UITouch touch in touches.Cast<UITouch>())
{
long id = touch.Handle.ToInt64();
FireEvent(this, id, TouchActionType.Pressed, touch, true); if (!idToTouchDictionary.ContainsKey(id))
{
idToTouchDictionary.Add(id, this);
}
} // Save the setting of the Capture property
capture = touchEffect.Capture;
} public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt); foreach (UITouch touch in touches.Cast<UITouch>())
{
long id = touch.Handle.ToInt64(); if (capture)
{
FireEvent(this, id, TouchActionType.Moved, touch, true);
}
else
{
CheckForBoundaryHop(touch); if (idToTouchDictionary[id] != null)
{
FireEvent(idToTouchDictionary[id], id, TouchActionType.Moved, touch, true);
}
}
}
} public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt); foreach (UITouch touch in touches.Cast<UITouch>())
{
long id = touch.Handle.ToInt64(); if (capture)
{
FireEvent(this, id, TouchActionType.Released, touch, false);
}
else
{
CheckForBoundaryHop(touch); if (idToTouchDictionary[id] != null)
{
FireEvent(idToTouchDictionary[id], id, TouchActionType.Released, touch, false);
}
}
idToTouchDictionary.Remove(id);
}
} public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
base.TouchesCancelled(touches, evt); foreach (UITouch touch in touches.Cast<UITouch>())
{
long id = touch.Handle.ToInt64(); if (capture)
{
FireEvent(this, id, TouchActionType.Cancelled, touch, false);
}
else if (idToTouchDictionary[id] != null)
{
FireEvent(idToTouchDictionary[id], id, TouchActionType.Cancelled, touch, false);
}
idToTouchDictionary.Remove(id);
}
} void CheckForBoundaryHop(UITouch touch)
{
long id = touch.Handle.ToInt64(); // TODO: Might require converting to a List for multiple hits
TouchRecognizer recognizerHit = null; foreach (UIView view in viewDictionary.Keys)
{
CGPoint location = touch.LocationInView(view); if (new CGRect(new CGPoint(), view.Frame.Size).Contains(location))
{
recognizerHit = viewDictionary[view];
}
}
if (recognizerHit != idToTouchDictionary[id])
{
if (idToTouchDictionary[id] != null)
{
FireEvent(idToTouchDictionary[id], id, TouchActionType.Exited, touch, true);
}
if (recognizerHit != null)
{
FireEvent(recognizerHit, id, TouchActionType.Entered, touch, true);
}
idToTouchDictionary[id] = recognizerHit;
}
} void FireEvent(TouchRecognizer recognizer, long id, TouchActionType actionType, UITouch touch, bool isInContact)
{
// Convert touch location to Xamarin.Forms Point value
CGPoint cgPoint = touch.LocationInView(recognizer.View);
Point xfPoint = new Point(cgPoint.X, cgPoint.Y); // Get the method to call for firing events
Action<Element, TouchActionEventArgs> onTouchAction = recognizer.touchEffect.OnTouchAction; // Call that method
onTouchAction(recognizer.element,
new TouchActionEventArgs(id, actionType, xfPoint, isInContact));
}
}
}

然后,在UWP的项目里面,加入这个类

using System;
using System.Linq;
using Windows.UI.Input;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Input;
using Xamarin.Forms;
using Xamarin.Forms.Platform.UWP; [assembly: ResolutionGroupName("XamarinDocs")]
[assembly: ExportEffect(typeof(TouchTracking.UWP.TouchEffect), "TouchEffect")] namespace TouchTracking.UWP
{
public class TouchEffect : PlatformEffect
{
FrameworkElement frameworkElement;
TouchTracking.TouchEffect effect;
Action<Element, TouchActionEventArgs> onTouchAction; protected override void OnAttached()
{
// Get the Windows FrameworkElement corresponding to the Element that the effect is attached to
frameworkElement = Control == null ? Container : Control; // Get access to the TouchEffect class in the .NET Standard library
effect = (TouchTracking.TouchEffect)Element.Effects.
FirstOrDefault(e => e is TouchTracking.TouchEffect); if (effect != null && frameworkElement != null)
{
// Save the method to call on touch events
onTouchAction = effect.OnTouchAction; // Set event handlers on FrameworkElement
frameworkElement.PointerEntered += OnPointerEntered;
frameworkElement.PointerPressed += OnPointerPressed;
frameworkElement.PointerMoved += OnPointerMoved;
frameworkElement.PointerReleased += OnPointerReleased;
frameworkElement.PointerExited += OnPointerExited;
frameworkElement.PointerCanceled += OnPointerCancelled;
}
} protected override void OnDetached()
{
if (onTouchAction != null)
{
// Release event handlers on FrameworkElement
frameworkElement.PointerEntered -= OnPointerEntered;
frameworkElement.PointerPressed -= OnPointerPressed;
frameworkElement.PointerMoved -= OnPointerMoved;
frameworkElement.PointerReleased -= OnPointerReleased;
frameworkElement.PointerExited -= OnPointerEntered;
frameworkElement.PointerCanceled -= OnPointerCancelled;
}
} void OnPointerEntered(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Entered, args);
} void OnPointerPressed(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Pressed, args); // Check setting of Capture property
if (effect.Capture)
{
(sender as FrameworkElement).CapturePointer(args.Pointer);
}
} void OnPointerMoved(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Moved, args);
} void OnPointerReleased(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Released, args);
} void OnPointerExited(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Exited, args);
} void OnPointerCancelled(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Cancelled, args);
} void CommonHandler(object sender, TouchActionType touchActionType, PointerRoutedEventArgs args)
{
PointerPoint pointerPoint = args.GetCurrentPoint(sender as UIElement);
Windows.Foundation.Point windowsPoint = pointerPoint.Position; onTouchAction(Element, new TouchActionEventArgs(args.Pointer.PointerId,
touchActionType,
new Point(windowsPoint.X, windowsPoint.Y),
args.Pointer.IsInContact));
}
}
}

这样,自定义的touch事件就完成了

在共享项目的xaml里面,类似这样即可使用:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App1"
xmlns:tt="clr-namespace:TouchTracking"

x:Class="App1.MainPage"> <Grid x:Name="p1" BackgroundColor="Aquamarine">
<Grid.Effects>
<tt:TouchEffect Capture="True" TouchAction="OnTouchEffectAction" />
</Grid.Effects> </Grid> </ContentPage>
05-17 14:07