Skip to content

Commit 0877d74

Browse files
bhavanesh2001PureWeen
authored andcommitted
[Android] Prevent Picker from Gaining Focus on Touch (#29068)
* make picker not focisable # Conflicts: # src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt * Address review comments * set keylistner to null * Make sure Focus()/Unfocus() works * Address review comments * Remove IsFocused Checks
1 parent 435ad86 commit 0877d74

File tree

8 files changed

+75
-85
lines changed

8 files changed

+75
-85
lines changed

src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue2339.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using NUnit.Framework;
22
using UITest.Appium;
33
using UITest.Core;
4-
54
namespace Microsoft.Maui.TestCases.Tests.Issues
65
{
76
public class Issue2339 : _IssuesUITest

src/Core/src/Handlers/Picker/PickerHandler.Android.cs

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,13 @@ protected override MauiPicker CreatePlatformView() =>
1919

2020
protected override void ConnectHandler(MauiPicker platformView)
2121
{
22-
platformView.FocusChange += OnFocusChange;
2322
platformView.Click += OnClick;
2423

2524
base.ConnectHandler(platformView);
2625
}
2726

2827
protected override void DisconnectHandler(MauiPicker platformView)
2928
{
30-
platformView.FocusChange -= OnFocusChange;
3129
platformView.Click -= OnClick;
3230

3331
base.DisconnectHandler(platformView);
@@ -86,25 +84,29 @@ public static void MapVerticalTextAlignment(IPickerHandler handler, IPicker pick
8684
handler.PlatformView?.UpdateVerticalAlignment(picker.VerticalTextAlignment);
8785
}
8886

89-
void OnFocusChange(object? sender, global::Android.Views.View.FocusChangeEventArgs e)
87+
internal static void MapFocus(IPickerHandler handler, IPicker picker, object? args)
9088
{
91-
if (PlatformView == null)
92-
return;
93-
94-
if (e.HasFocus)
89+
if (handler.IsConnected())
9590
{
96-
if (PlatformView.Clickable)
97-
PlatformView.CallOnClick();
98-
else
99-
OnClick(PlatformView, EventArgs.Empty);
91+
ViewHandler.MapFocus(handler, picker, args);
92+
handler.PlatformView.CallOnClick();
10093
}
101-
else if (_dialog != null)
94+
}
95+
96+
internal static void MapUnfocus(IPickerHandler handler, IPicker picker, object? args)
97+
{
98+
if (handler.IsConnected() && handler is PickerHandler pickerHandler)
10299
{
103-
_dialog.Hide();
104-
_dialog = null;
100+
pickerHandler.DismissDialog();
101+
ViewHandler.MapUnfocus(handler,picker,args);
105102
}
106103
}
107104

105+
void DismissDialog()
106+
{
107+
_dialog?.Dismiss();
108+
}
109+
108110
void OnClick(object? sender, EventArgs e)
109111
{
110112
if (_dialog == null && VirtualView != null)
@@ -152,15 +154,37 @@ void OnClick(object? sender, EventArgs e)
152154

153155
_dialog.SetCanceledOnTouchOutside(true);
154156

155-
_dialog.DismissEvent += (sender, args) =>
156-
{
157-
_dialog = null;
158-
};
157+
_dialog.ShowEvent += OnDialogShown;
158+
159+
_dialog.DismissEvent += OnDialogDismiss;
159160

160161
_dialog.Show();
161162
}
162163
}
163164

165+
void OnDialogDismiss(object? sender, EventArgs e)
166+
{
167+
if (_dialog is null)
168+
{
169+
return;
170+
}
171+
172+
_dialog.DismissEvent -= OnDialogDismiss;
173+
VirtualView.IsFocused = false;
174+
_dialog = null;
175+
}
176+
177+
void OnDialogShown(object? sender, EventArgs e)
178+
{
179+
if (_dialog is null)
180+
{
181+
return;
182+
}
183+
184+
_dialog.ShowEvent -= OnDialogShown;
185+
VirtualView.IsFocused = true;
186+
}
187+
164188
static void Reload(IPickerHandler handler)
165189
{
166190
handler.PlatformView.UpdatePicker(handler.VirtualView);

src/Core/src/Handlers/Picker/PickerHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ public partial class PickerHandler : IPickerHandler
3232

3333
public static CommandMapper<IPicker, IPickerHandler> CommandMapper = new(ViewCommandMapper)
3434
{
35+
#if ANDROID
36+
[nameof(IPicker.Focus)] = MapFocus,
37+
[nameof(IPicker.Unfocus)] = MapUnfocus
38+
#endif
3539
};
3640

3741
public PickerHandler() : base(Mapper, CommandMapper)

src/Core/src/Platform/Android/MauiDatePicker.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Android.Content;
33
using Android.Runtime;
44
using Android.Text;
5+
using Android.Text.Method;
56
using Android.Util;
67
using Android.Views;
78
using AndroidX.AppCompat.Widget;
@@ -31,6 +32,10 @@ protected MauiDatePicker(IntPtr javaReference, JniHandleOwnership transfer) : ba
3132
{
3233
}
3334

35+
// MovementMethod handles cursor positioning, scrolling, and text selection (per Android docs).
36+
// Since text is readonly, we disable it to avoid unnecessary cursor navigation during keyboard input.
37+
protected override IMovementMethod? DefaultMovementMethod => null;
38+
3439
public Action? ShowPicker { get; set; }
3540
public Action? HidePicker { get; set; }
3641

@@ -44,10 +49,7 @@ void Initialize()
4449
if (Background != null)
4550
DrawableCompat.Wrap(Background);
4651

47-
Focusable = true;
48-
FocusableInTouchMode = false;
49-
Clickable = true;
50-
InputType = InputTypes.Null;
52+
PickerManager.Init(this);
5153

5254
SetOnClickListener(this);
5355
}
Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Android.Content;
22
using Android.Runtime;
3+
using Android.Text.Method;
34
using Android.Views;
45
using AndroidX.AppCompat.Widget;
56
using AndroidX.Core.Graphics.Drawable;
@@ -9,25 +10,12 @@ namespace Microsoft.Maui.Platform
910
{
1011
public class MauiPicker : MauiPickerBase
1112
{
12-
public bool ShowPopupOnFocus { get; set; }
1313

1414
public MauiPicker(Context context) : base(context)
1515
{
1616
PickerManager.Init(this);
1717
}
1818

19-
public override bool OnTouchEvent(MotionEvent? e)
20-
{
21-
PickerManager.OnTouchEvent(this, e);
22-
return base.OnTouchEvent(e); // Raises the OnClick event if focus is already received
23-
}
24-
25-
protected override void OnFocusChanged(bool gainFocus, [GeneratedEnum] FocusSearchDirection direction, ARect? previouslyFocusedRect)
26-
{
27-
base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect);
28-
PickerManager.OnFocusChanged(gainFocus, this);
29-
}
30-
3119
protected override void Dispose(bool disposing)
3220
{
3321
if (disposing)
@@ -44,5 +32,9 @@ public MauiPickerBase(Context context) : base(context)
4432
if (Background != null)
4533
DrawableCompat.Wrap(Background);
4634
}
35+
36+
// MovementMethod handles cursor positioning, scrolling, and text selection (per Android docs).
37+
// Since text is readonly, we disable it to avoid unnecessary cursor navigation during keyboard input.
38+
protected override IMovementMethod? DefaultMovementMethod => null;
4739
}
4840
}

src/Core/src/Platform/Android/MauiTimePicker.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Android.Content;
33
using Android.Runtime;
44
using Android.Text;
5+
using Android.Text.Method;
56
using Android.Util;
67
using Android.Views;
78
using AndroidX.AppCompat.Widget;
@@ -31,6 +32,10 @@ protected MauiTimePicker(IntPtr javaReference, JniHandleOwnership transfer) : ba
3132
{
3233
}
3334

35+
// MovementMethod handles cursor positioning, scrolling, and text selection (per Android docs).
36+
// Since text is readonly, we disable it to avoid unnecessary cursor navigation during keyboard input.
37+
protected override IMovementMethod? DefaultMovementMethod => null;
38+
3439
public Action? ShowPicker { get; set; }
3540
public Action? HidePicker { get; set; }
3641

@@ -44,10 +49,7 @@ void Initialize()
4449
if (Background != null)
4550
DrawableCompat.Wrap(Background);
4651

47-
Focusable = true;
48-
FocusableInTouchMode = false;
49-
Clickable = true;
50-
InputType = InputTypes.Null;
52+
PickerManager.Init(this);
5153

5254
SetOnClickListener(this);
5355
}

src/Core/src/Platform/Android/PickerManager.cs

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,60 +11,20 @@ namespace Microsoft.Maui.Platform
1111
{
1212
internal static class PickerManager
1313
{
14-
readonly static HashSet<Keycode> AvailableKeys = new HashSet<Keycode>(new[] {
15-
Keycode.Tab, Keycode.Forward, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
16-
});
17-
1814
public static void Init(EditText editText)
1915
{
2016
editText.Focusable = true;
21-
editText.Clickable = true;
22-
editText.InputType = InputTypes.Null;
23-
24-
editText.KeyPress += OnKeyPress;
25-
}
26-
27-
public static void OnTouchEvent(EditText sender, MotionEvent? e)
28-
{
29-
if (e != null && e.Action == MotionEventActions.Up && !sender.IsFocused)
30-
{
31-
sender.RequestFocus();
32-
}
33-
}
34-
35-
public static void OnFocusChanged(bool gainFocus, EditText sender)
36-
{
37-
if (gainFocus)
38-
sender.CallOnClick();
39-
}
17+
editText.FocusableInTouchMode = false;
18+
editText.Clickable = true;
4019

41-
static void OnKeyPress(object? sender, AView.KeyEventArgs e)
42-
{
43-
//To prevent user from entering text when focus is received
44-
e.Handled = true;
45-
if (!AvailableKeys.Contains(e.KeyCode))
46-
{
47-
return;
48-
}
49-
(sender as AView)?.CallOnClick();
20+
// InputType needs to be set before setting KeyListener
21+
editText.InputType = InputTypes.Null;
22+
editText.KeyListener = null;
5023
}
5124

5225
public static void Dispose(EditText editText)
5326
{
54-
editText.KeyPress -= OnKeyPress;
5527
editText.SetOnClickListener(null);
5628
}
57-
58-
public static Java.Lang.ICharSequence GetTitle(Color titleColor, string title)
59-
{
60-
if (titleColor == null)
61-
return new Java.Lang.String(title);
62-
63-
var spannableTitle = new SpannableString(title ?? string.Empty);
64-
#pragma warning disable CA1416 // https://github.com/xamarin/xamarin-android/issues/6962
65-
spannableTitle.SetSpan(new ForegroundColorSpan(titleColor.ToPlatform()), 0, spannableTitle.Length(), SpanTypes.ExclusiveExclusive);
66-
#pragma warning restore CA1416
67-
return spannableTitle;
68-
}
6929
}
7030
}

src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
#nullable enable
2+
*REMOVED*Microsoft.Maui.Platform.MauiPicker.ShowPopupOnFocus.get -> bool
3+
*REMOVED*Microsoft.Maui.Platform.MauiPicker.ShowPopupOnFocus.set -> void
4+
*REMOVED*override Microsoft.Maui.Platform.MauiPicker.OnFocusChanged(bool gainFocus, Android.Views.FocusSearchDirection direction, Android.Graphics.Rect? previouslyFocusedRect) -> void
5+
*REMOVED*override Microsoft.Maui.Platform.MauiPicker.OnTouchEvent(Android.Views.MotionEvent? e) -> bool
6+
override Microsoft.Maui.Platform.MauiDatePicker.DefaultMovementMethod.get -> Android.Text.Method.IMovementMethod?
7+
override Microsoft.Maui.Platform.MauiPickerBase.DefaultMovementMethod.get -> Android.Text.Method.IMovementMethod?
8+
override Microsoft.Maui.Platform.MauiTimePicker.DefaultMovementMethod.get -> Android.Text.Method.IMovementMethod?
29
virtual Microsoft.Maui.Animations.Lerp.LerpDelegate.Invoke(object! start, object! end, double progress) -> object!
310
Microsoft.Maui.Handlers.ContextFlyoutItemHandlerUpdate.ContextFlyoutItemHandlerUpdate(Microsoft.Maui.Handlers.ContextFlyoutItemHandlerUpdate! original) -> void
411
Microsoft.Maui.Handlers.ContextFlyoutItemHandlerUpdate.Deconstruct(out int Index, out Microsoft.Maui.IMenuElement! MenuElement) -> void

0 commit comments

Comments
 (0)