Xamarin.Forms实现touch事件
Xamarin.Forms的View没有touch事件,只能自己实现
首先,在共享项目里面,放入这几个类,结构大概是这样的:
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; } = 75; public SKColor Color { set; get; } = new SKColor(0, 0, 255, 64); 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, 2) + Math.Pow(pt.Y - Center.Y, 2)) < (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[2]; 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[0] + motionEvent.GetX(pointerIndex), twoIntArray[1] + 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 = 0; pointerIndex < motionEvent.PointerCount; pointerIndex++) { id = motionEvent.GetPointerId(pointerIndex); if (capture) { senderView.GetLocationOnScreen(twoIntArray); screenPointerCoords = new Point(twoIntArray[0] + motionEvent.GetX(pointerIndex), twoIntArray[1] + 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[0], twoIntArray[1], 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[0]; double y = pointerLocation.Y - twoIntArray[1]; 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>