You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 23:55:31