从Xamarin.Forms迁移至.NET MAUI:实现TouchTracking触摸功能方案咨询
在.NET MAUI中替代TouchTracking实现手指绘图触摸功能
由于原Xamarin.Forms的TouchTracking包无法兼容.NET MAUI,你可以通过以下两种方案实现相同的触摸检测、多点触摸及坐标获取功能,适配手指绘图场景:
方案一:自定义TouchEffect(贴近原API结构)
该方案完全模拟原TouchTracking的API设计,无需大幅修改现有业务逻辑。
1. 定义跨平台触摸效果类
先创建基础的触摸事件参数和效果类:
using Microsoft.Maui.Controls; using Microsoft.Maui.Graphics; public class TouchEffect : RoutingEffect { public event EventHandler<TouchActionEventArgs>? TouchAction; public TouchEffect() : base("YourNamespace.TouchEffect") { } public bool Capture { get; set; } public void OnTouchAction(object sender, TouchActionEventArgs args) { TouchAction?.Invoke(sender, args); } } 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 { get; } public TouchActionType Type { get; } public Point Location { get; } public bool IsInContact { get; } } public enum TouchActionType { Pressed, Moved, Released, Cancelled }
2. 实现各平台原生触摸逻辑
Android平台(Platforms/Android/TouchEffect_Android.cs)
using Android.Views; using Microsoft.Maui.Controls.Platform; using Microsoft.Maui.Graphics; using View = Android.Views.View; [assembly: ResolutionGroupName("YourNamespace")] [assembly: ExportEffect(typeof(YourNamespace.Platforms.Android.TouchEffect_Android), "TouchEffect")] namespace YourNamespace.Platforms.Android { public class TouchEffect_Android : PlatformEffect { private View? _view; private TouchEffect? _touchEffect; private Dictionary<int, long> _touchIdMap = new(); private long _nextId = 1; protected override void OnAttached() { _view = Control ?? Container; _touchEffect = Element.Effects.FirstOrDefault(e => e is TouchEffect) as TouchEffect; if (_view != null && _touchEffect != null) { _view.Touch += OnTouch; } } protected override void OnDetached() { if (_view != null) { _view.Touch -= OnTouch; } } private void OnTouch(object? sender, View.TouchEventArgs e) { if (_touchEffect == null) return; var motionEvent = e.Event; var action = motionEvent.ActionMasked; if (action is MotionEventActions.Down or MotionEventActions.PointerDown or MotionEventActions.Move or MotionEventActions.Up or MotionEventActions.PointerUp or MotionEventActions.Cancel) { int pointerIndex = motionEvent.ActionIndex; int pointerId = motionEvent.GetPointerId(pointerIndex); if (!_touchIdMap.TryGetValue(pointerId, out long id)) { id = _nextId++; _touchIdMap.Add(pointerId, id); } Point location = new(motionEvent.GetX(pointerIndex), motionEvent.GetY(pointerIndex)); TouchActionType touchActionType = action switch { MotionEventActions.Down or MotionEventActions.PointerDown => TouchActionType.Pressed, MotionEventActions.Move => TouchActionType.Moved, MotionEventActions.Up or MotionEventActions.PointerUp => TouchActionType.Released, MotionEventActions.Cancel => TouchActionType.Cancelled, _ => TouchActionType.Cancelled }; _touchEffect.OnTouchAction(Element, new TouchActionEventArgs(id, touchActionType, location, action != MotionEventActions.Up && action != MotionEventActions.PointerUp && action != MotionEventActions.Cancel)); if (action is MotionEventActions.Up or MotionEventActions.PointerUp or MotionEventActions.Cancel) { _touchIdMap.Remove(pointerId); } if (_touchEffect.Capture) { e.Handled = true; } } } } }
iOS平台(Platforms/iOS/TouchEffect_iOS.cs)
using UIKit; using Microsoft.Maui.Controls.Platform; using Microsoft.Maui.Graphics; [assembly: ResolutionGroupName("YourNamespace")] [assembly: ExportEffect(typeof(YourNamespace.Platforms.iOS.TouchEffect_iOS), "TouchEffect")] namespace YourNamespace.Platforms.iOS { public class TouchEffect_iOS : PlatformEffect { private UIView? _view; private TouchEffect? _touchEffect; private Dictionary<nint, long> _touchIdMap = new(); private long _nextId = 1; protected override void OnAttached() { _view = Control ?? Container; _touchEffect = Element.Effects.FirstOrDefault(e => e is TouchEffect) as TouchEffect; if (_view != null && _touchEffect != null) { _view.UserInteractionEnabled = true; _view.AddGestureRecognizer(new TouchGestureRecognizer(_touchEffect, _touchIdMap, ref _nextId)); } } protected override void OnDetached() { if (_view != null) { var recognizer = _view.GestureRecognizers.FirstOrDefault(g => g is TouchGestureRecognizer) as TouchGestureRecognizer; if (recognizer != null) { _view.RemoveGestureRecognizer(recognizer); } } } } public class TouchGestureRecognizer : UIGestureRecognizer { private readonly TouchEffect _touchEffect; private readonly Dictionary<nint, long> _touchIdMap; private readonly ref long _nextId; public TouchGestureRecognizer(TouchEffect touchEffect, Dictionary<nint, long> touchIdMap, ref long nextId) { _touchEffect = touchEffect; _touchIdMap = touchIdMap; _nextId = nextId; CancelsTouchesInView = false; } public override void TouchesBegan(NSSet touches, UIEvent evt) { base.TouchesBegan(touches, evt); HandleTouches(touches, evt, TouchActionType.Pressed); if (_touchEffect.Capture) State = UIGestureRecognizerState.Began; } public override void TouchesMoved(NSSet touches, UIEvent evt) { base.TouchesMoved(touches, evt); HandleTouches(touches, evt, TouchActionType.Moved); if (_touchEffect.Capture) State = UIGestureRecognizerState.Changed; } public override void TouchesEnded(NSSet touches, UIEvent evt) { base.TouchesEnded(touches, evt); HandleTouches(touches, evt, TouchActionType.Released); if (_touchEffect.Capture) State = UIGestureRecognizerState.Ended; } public override void TouchesCancelled(NSSet touches, UIEvent evt) { base.TouchesCancelled(touches, evt); HandleTouches(touches, evt, TouchActionType.Cancelled); if (_touchEffect.Capture) State = UIGestureRecognizerState.Cancelled; } private void HandleTouches(NSSet touches, UIEvent evt, TouchActionType actionType) { foreach (UITouch touch in touches.Cast<UITouch>()) { if (!_touchIdMap.TryGetValue(touch.Handle, out long id)) { id = _nextId++; _touchIdMap.Add(touch.Handle, id); } Point location = touch.LocationInView(View); bool isInContact = actionType != TouchActionType.Released && actionType != TouchActionType.Cancelled; _touchEffect.OnTouchAction(Element, new TouchActionEventArgs(id, actionType, location, isInContact)); if (actionType is TouchActionType.Released or TouchActionType.Cancelled) { _touchIdMap.Remove(touch.Handle); } } } } }
3. XAML中使用自定义效果
替换原有命名空间后,代码和原Xamarin.Forms版本几乎一致:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:skia="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls" xmlns:local="clr-namespace:YourNamespace" x:Class="YourNamespace.DrawPage"> <Grid BackgroundColor="White"> <skia:SKCanvasView x:Name="canvasView" PaintSurface="PaintingCanvasEnvent" /> <Grid.Effects> <local:TouchEffect Capture="True" TouchAction="OnTouchEffectAction"/> </Grid.Effects> </Grid> </ContentPage>
4. 复用原有后台逻辑
仅需调整坐标转换,原有绘图逻辑可直接复用:
void OnTouchEffectAction(object sender, TouchActionEventArgs args) { SKPoint current = new SKPoint((float)args.Location.X, (float)args.Location.Y); switch (args.Type) { case TouchActionType.Pressed: // 原有按下逻辑,如记录起始点 break; case TouchActionType.Moved: // 原有移动逻辑,如绘制线条 canvasView.InvalidateSurface(); break; case TouchActionType.Released: // 原有释放逻辑,如结束路径 canvasView.InvalidateSurface(); break; case TouchActionType.Cancelled: // 原有取消逻辑,如丢弃路径 break; } }
方案二:直接使用SKCanvasView原生触摸事件(更简洁)
SkiaSharp.Views.Maui的SKCanvasView自带触摸事件,无需自定义效果,适合触摸逻辑仅与绘图绑定的场景:
1. XAML调整
移除Grid的Effect,直接给SKCanvasView绑定触摸事件:
<Grid BackgroundColor="White"> <skia:SKCanvasView x:Name="canvasView" PaintSurface="PaintingCanvasEnvent" Touch="OnCanvasTouch"/> </Grid>
2. 后台处理触摸事件
SKTouchEventArgs内置触摸ID、位置、动作类型,完全支持多点触摸:
void OnCanvasTouch(object sender, SKTouchEventArgs e) { SKPoint current = e.Location; switch (e.ActionType) { case SKTouchAction.Pressed: // 处理按下逻辑 e.Handled = true; break; case SKTouchAction.Moved: // 处理移动逻辑 canvasView.InvalidateSurface(); e.Handled = true; break; case SKTouchAction.Released: // 处理释放逻辑 canvasView.InvalidateSurface(); e.Handled = true; break; case SKTouchAction.Cancelled: // 处理取消逻辑 e.Handled = true; break; } }
内容的提问来源于stack exchange,提问作者Mr Hery
相关产品推荐
相关产品推荐

