Android平台Xamarin Entry切换IsPassword时光标重置为0的解决办法
问题描述
我用Xamarin的Entry控件做密码输入框,加了一个ImageButton,长按按钮就能临时显示密码。代码如下,Entry的IsPassword属性绑定到按钮的IsPressed属性(通过布尔反转转换器):
<Entry Text="{Binding Password}" Keyboard="Plain" IsPassword="{Binding Source={x:Reference previewPasswordButton}, Path=IsPressed, Converter={StaticResource boolInvertConverter}}"> </Entry>
现在碰到个问题:按下并释放按钮时,光标会直接跳到输入框开头,但Entry的焦点并没有变化(至少没触发焦点相关的属性变更事件)。这对正在输入密码、想快速核对已输入内容的用户来说太麻烦了,我希望光标能保持原来的位置。
我试过加PropertyChanged处理器来缓存并恢复光标位置,但事件执行顺序的问题导致实现起来既困难又繁琐,有没有更省心的解决方案?
解决方案
方法一:用Behavior封装光标保护逻辑
写一个专门的Behavior,自动在IsPassword属性变化前后保存和恢复光标位置,不用手动处理事件顺序的坑:
using Xamarin.Forms; public class PreserveCursorPositionBehavior : Behavior<Entry> { private int _cursorPosition; private int _selectionLength; protected override void OnAttachedTo(Entry entry) { base.OnAttachedTo(entry); entry.PropertyChanged += Entry_PropertyChanged; } protected override void OnDetachingFrom(Entry entry) { base.OnDetachingFrom(entry); entry.PropertyChanged -= Entry_PropertyChanged; } private void Entry_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { var entry = sender as Entry; if (entry == null) return; // 准备显示明文时,先存下当前光标位置和选中长度 if (e.PropertyName == nameof(Entry.IsPassword) && !entry.IsPassword) { _cursorPosition = entry.CursorPosition; _selectionLength = entry.SelectionLength; } // 切回密码模式后,延迟恢复光标位置(等控件刷新完) else if (e.PropertyName == nameof(Entry.IsPassword) && entry.IsPassword) { Device.BeginInvokeOnMainThread(() => { entry.CursorPosition = _cursorPosition; entry.SelectionLength = _selectionLength; }); } } }
然后在XAML里给Entry挂上这个Behavior就行:
<Entry Text="{Binding Password}" Keyboard="Plain" IsPassword="{Binding Source={x:Reference previewPasswordButton}, Path=IsPressed, Converter={StaticResource boolInvertConverter}}"> <Entry.Behaviors> <local:PreserveCursorPositionBehavior /> </Entry.Behaviors> </Entry>
这个方法把光标管理逻辑封装在独立组件里,页面后台不用写冗余代码,而且通过Device.BeginInvokeOnMainThread避开了控件刷新覆盖光标位置的问题。
方法二:自定义Entry控件(全局复用更方便)
如果多个页面都需要这个功能,直接自定义一个Entry控件,把光标保护逻辑内置进去:
public class PasswordEntry : Entry { private int _lastCursorPos; private int _lastSelectionLength; protected override void OnPropertyChanged(string propertyName = null) { if (propertyName == nameof(IsPassword)) { // 切换到明文前存光标位置 if (!IsPassword) { _lastCursorPos = CursorPosition; _lastSelectionLength = SelectionLength; } // 切回密码后恢复光标 else { Device.BeginInvokeOnMainThread(() => { CursorPosition = _lastCursorPos; SelectionLength = _lastSelectionLength; }); } } base.OnPropertyChanged(propertyName); } }
之后直接在XAML里用这个自定义控件代替原生Entry:
<local:PasswordEntry Text="{Binding Password}" Keyboard="Plain" IsPassword="{Binding Source={x:Reference previewPasswordButton}, Path=IsPressed, Converter={StaticResource boolInvertConverter}}"> </local:PasswordEntry>
这种方式适合全局复用,不用每个Entry都手动加Behavior,代码更清爽。
核心注意点
- 必须用
Device.BeginInvokeOnMainThread延迟恢复光标:因为IsPassword变化后,控件内部会自动刷新布局,直接设置光标位置会被覆盖,延迟执行能确保控件刷新完成后再恢复光标。 - 两种方法都把逻辑封装起来,避开了手动处理事件顺序的麻烦,代码更易维护。
内容的提问来源于stack exchange,提问作者Pete Roberts
相关产品推荐
相关产品推荐

