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

Android平台Entry/Editor点击:先显光标再执行TapGestureRecognizer并获取光标位置

解决Android平台Entry/Editor点击时获取当前光标位置的问题

核心问题是跨平台TapGestureRecognizer的触发时机早于Android原生控件的光标定位逻辑,导致拿到的是上一次的光标位置。下面是针对Entry和Editor的统一解决方案,直接基于原生触摸事件拦截,确保光标定位完成后再获取正确位置。

实现步骤

1. 共享项目定义附加属性(复用逻辑)

创建一个附加属性,用来在Entry/Editor和ViewModel之间传递光标位置:

using Microsoft.Maui.Controls;

namespace YourAppNamespace;

public static class CursorPositionHelper
{
    public static readonly BindableProperty TapCursorPositionProperty =
        BindableProperty.CreateAttached(
            "TapCursorPosition",
            typeof(int),
            typeof(CursorPositionHelper),
            default(int));

    public static int GetTapCursorPosition(BindableObject view)
    {
        return (int)view.GetValue(TapCursorPositionProperty);
    }

    public static void SetTapCursorPosition(BindableObject view, int value)
    {
        view.SetValue(TapCursorPositionProperty, value);
    }
}

2. Android平台实现原生触摸监听

通过自定义Handler(MAUI)或Renderer(Xamarin.Forms),拦截原生触摸事件,在系统完成光标定位后获取位置。

MAUI版本:EntryHandler扩展

using Android.Views;
using Microsoft.Maui.Platform;

namespace YourAppNamespace.Platforms.Android;

public static class EntryCursorHandler
{
    public static void ConfigureCursorPositionListener(this EntryHandler handler)
    {
        if (handler.PlatformView is not Android.Widget.EditText editText)
            return;

        editText.SetOnTouchListener(new CursorTouchListener(handler.VirtualView));
    }

    private class CursorTouchListener : Java.Lang.Object, IOnTouchListener
    {
        private readonly Entry _entry;

        public CursorTouchListener(Entry entry)
        {
            _entry = entry;
        }

        public bool OnTouch(View v, MotionEvent e)
        {
            if (e.Action == MotionEventActions.Up)
            {
                // 用Post把获取操作放到系统光标定位逻辑之后执行
                v.Post(() =>
                {
                    if (v is Android.Widget.EditText editText)
                    {
                        int cursorPos = editText.SelectionStart;
                        CursorPositionHelper.SetTapCursorPosition(_entry, cursorPos);
                    }
                });
            }
            // 返回false,让系统继续处理原生触摸逻辑(显示光标、弹出键盘等)
            return false;
        }
    }
}

MAUI版本:EditorHandler扩展

逻辑和Entry完全一致,只是替换对应的控件类型:

using Android.Views;
using Microsoft.Maui.Platform;

namespace YourAppNamespace.Platforms.Android;

public static class EditorCursorHandler
{
    public static void ConfigureCursorPositionListener(this EditorHandler handler)
    {
        if (handler.PlatformView is not Android.Widget.EditText editText)
            return;

        editText.SetOnTouchListener(new EditorCursorTouchListener(handler.VirtualView));
    }

    private class EditorCursorTouchListener : Java.Lang.Object, IOnTouchListener
    {
        private readonly Editor _editor;

        public EditorCursorTouchListener(Editor editor)
        {
            _editor = editor;
        }

        public bool OnTouch(View v, MotionEvent e)
        {
            if (e.Action == MotionEventActions.Up)
            {
                v.Post(() =>
                {
                    if (v is Android.Widget.EditText editText)
                    {
                        int cursorPos = editText.SelectionStart;
                        CursorPositionHelper.SetTapCursorPosition(_editor, cursorPos);
                    }
                });
            }
            return false;
        }
    }
}

MAUI注册Handler

在MauiProgram.cs中注册自定义Handler:

using YourAppNamespace.Platforms.Android;

namespace YourAppNamespace;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureMauiHandlers(handlers =>
            {
#if ANDROID
                handlers.AddHandler<Entry, EntryHandler>(handler =>
                {
                    handler.ConfigureCursorPositionListener();
                });
                handlers.AddHandler<Editor, EditorHandler>(handler =>
                {
                    handler.ConfigureCursorPositionListener();
                });
#endif
            });

        return builder.Build();
    }
}

3. XAML中绑定使用

在Entry/Editor上绑定附加属性,将光标位置同步到ViewModel:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:YourAppNamespace"
             x:Class="YourAppNamespace.MainPage">

    <StackLayout>
        <Entry local:CursorPositionHelper.TapCursorPosition="{Binding EntryCursorPos, Mode=OneWayToSource}"
               Placeholder="点击输入框测试光标位置"/>
        
        <Editor local:CursorPositionHelper.TapCursorPosition="{Binding EditorCursorPos, Mode=OneWayToSource}"
                Placeholder="点击编辑器测试光标位置"
                HeightRequest="150"/>
    </StackLayout>
</ContentPage>

4. ViewModel接收光标位置

在ViewModel中定义对应属性,接收同步过来的光标位置:

using ObservableObject = CommunityToolkit.Mvvm.ComponentModel.ObservableObject;

namespace YourAppNamespace;

public class MainPageViewModel : ObservableObject
{
    private int _entryCursorPos;
    public int EntryCursorPos
    {
        get => _entryCursorPos;
        set => SetProperty(ref _entryCursorPos, value);
    }

    private int _editorCursorPos;
    public int EditorCursorPos
    {
        get => _editorCursorPos;
        set => SetProperty(ref _editorCursorPos, value);
    }
}

关键说明

  • 放弃使用TapGestureRecognizer,改用原生触摸监听,避免触发时机冲突。
  • v.Post()方法确保获取光标位置的逻辑在系统完成光标定位后执行,拿到的是当前点击的正确位置。
  • 返回false保留原生控件的所有默认行为(显示光标、弹出键盘、长按选中等)。
  • 附加属性的方式让Entry和Editor复用同一套逻辑,无需单独编写事件处理。

内容的提问来源于stack exchange,提问作者osama alqaisi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 20:13:23