Skip to content

Commit 2faddc2

Browse files
kubafloPureWeen
andauthored
[Android] Improve Keyboard Accessibility: Support Spacebar for Android Gesture recognizers (#29649)
* Improve Keyboard Accessibility: Support Spacebar for Android Gesture Recognizers * Update GesturePlatformManager.Android.cs * - update code a bit * Update ViewExtensions.cs --------- Co-authored-by: Shane Neuville <shneuvil@microsoft.com>
1 parent 3f0a7c6 commit 2faddc2

File tree

5 files changed

+92
-8
lines changed

5 files changed

+92
-8
lines changed

src/Controls/src/Core/Platform/Android/Extensions/SemanticExtensions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public static void UpdateSemanticNodeInfo(this View virtualView, AccessibilityNo
1111
if (info == null)
1212
return;
1313

14-
if (virtualView.TapGestureRecognizerNeedsDelegate())
14+
if (virtualView.HasAccessibleTapGesture())
1515
info.AddAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ActionClick);
1616
}
1717

@@ -46,5 +46,9 @@ internal static void AddOrRemoveControlsAccessibilityDelegate(this View virtualV
4646
}
4747
}
4848
}
49+
50+
internal static bool ControlsAccessibilityDelegateNeeded(this View virtualView)
51+
=> virtualView.HasAccessibleTapGesture();
52+
4953
}
5054
}

src/Controls/src/Core/Platform/GestureManager/GesturePlatformManager.Android.cs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
using System.ComponentModel;
66
using Android.Content;
77
using Android.Views;
8+
using AndroidX.AppCompat.Widget;
89
using AndroidX.Core.View;
910
using Microsoft.Maui.Controls.Internals;
1011
using Microsoft.Maui.Graphics;
12+
using static Android.Views.View;
1113
using AView = Android.Views.View;
1214

1315
namespace Microsoft.Maui.Controls.Platform
@@ -22,6 +24,8 @@ class GesturePlatformManager : IDisposable
2224
bool _disposed;
2325
bool _inputTransparent;
2426
bool _isEnabled;
27+
bool? _focusableDefaultValue;
28+
bool? _clickableDefaultValue;
2529
protected virtual VisualElement? Element => _handler?.VirtualView as VisualElement;
2630

2731
View? View => Element as View;
@@ -159,7 +163,7 @@ ScaleGestureDetector InitializeScaleDetector()
159163

160164
bool ViewHasPinchGestures()
161165
{
162-
if (View == null)
166+
if (View is null)
163167
return false;
164168

165169
int count = View.GestureRecognizers.Count;
@@ -210,14 +214,63 @@ void SetupGestures()
210214
}
211215

212216
// Always unsubscribe first to avoid duplicates
217+
213218
platformView.Touch -= OnPlatformViewTouched;
219+
platformView.KeyPress -= OnKeyPress;
220+
214221

215222
if (shouldAddTouchEvent)
216223
{
217224
platformView.Touch += OnPlatformViewTouched;
225+
226+
// If we have a TapGestureRecognizer, we need to handle key presses
227+
if (View.HasAccessibleTapGesture())
228+
{
229+
platformView.KeyPress += OnKeyPress;
230+
_focusableDefaultValue ??= platformView.Focusable;
231+
_clickableDefaultValue ??= platformView.Clickable;
232+
platformView.Focusable = true;
233+
platformView.Clickable = true;
234+
}
235+
}
236+
else
237+
{
238+
_focusableDefaultValue = null;
239+
_clickableDefaultValue = null;
218240
}
219241
}
220242

243+
void OnKeyPress(object? sender, KeyEventArgs e)
244+
{
245+
if (e.Event?.Action != KeyEventActions.Up)
246+
{
247+
e.Handled = false;
248+
return;
249+
}
250+
251+
if (View is null || sender is not AView platformView)
252+
{
253+
e.Handled = false;
254+
return;
255+
}
256+
257+
if (e.KeyCode.IsConfirmKey() &&
258+
View.HasAccessibleTapGesture(out var tapGestureRecognizer) &&
259+
e.Event.HasNoModifiers)
260+
{
261+
if (!platformView.Enabled)
262+
{
263+
e.Handled = true;
264+
return;
265+
}
266+
267+
if (!e.Event.IsCanceled)
268+
tapGestureRecognizer.SendTapped(View, (v) => Point.Zero);
269+
}
270+
271+
e.Handled = false;
272+
}
273+
221274
void OnPlatformViewTouched(object? sender, AView.TouchEventArgs e)
222275
{
223276
if (_disposed)
@@ -236,8 +289,15 @@ void OnPlatformViewTouched(object? sender, AView.TouchEventArgs e)
236289
void SetupElement(VisualElement? oldElement, VisualElement? newElement)
237290
{
238291
var platformView = Control;
239-
if (platformView != null)
292+
if (platformView is not null)
293+
{
294+
platformView.Focusable = _focusableDefaultValue ?? platformView.Focusable;
295+
platformView.Clickable = _clickableDefaultValue ?? platformView.Clickable;
296+
_focusableDefaultValue = null;
297+
_clickableDefaultValue = null;
240298
platformView.Touch -= OnPlatformViewTouched;
299+
platformView.KeyPress -= OnKeyPress;
300+
}
241301

242302
_handler = null;
243303
if (oldElement != null)

src/Controls/src/Core/Platform/GestureManager/GesturePlatformManager.iOS.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ void LoadRecognizers()
585585

586586
if (PlatformView != null &&
587587
_handler.VirtualView is View v &&
588-
v.TapGestureRecognizerNeedsDelegate() &&
588+
v.HasAccessibleTapGesture() &&
589589
(PlatformView.AccessibilityTraits & UIAccessibilityTrait.Button) != UIAccessibilityTrait.Button)
590590
{
591591
PlatformView.AccessibilityTraits |= UIAccessibilityTrait.Button;

src/Controls/src/Core/Platform/SemanticExtensions.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
1-
namespace Microsoft.Maui.Controls.Platform
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Microsoft.Maui.Controls.Platform
24
{
35
public static partial class SemanticExtensions
46
{
5-
internal static bool ControlsAccessibilityDelegateNeeded(this View virtualView)
6-
=> virtualView.TapGestureRecognizerNeedsDelegate();
7+
internal static bool HasAccessibleTapGesture(this View virtualView) =>
8+
HasAccessibleTapGesture(virtualView, out _);
79

8-
internal static bool TapGestureRecognizerNeedsDelegate(this View virtualView)
10+
internal static bool HasAccessibleTapGesture(
11+
this View virtualView,
12+
[NotNullWhen(true)] out TapGestureRecognizer? tapGestureRecognizer)
913
{
1014
foreach (var gesture in virtualView.GestureRecognizers)
1115
{
1216
//Accessibility can't handle Tap Recognizers with > 1 tap
1317
if (gesture is TapGestureRecognizer tgr && tgr.NumberOfTapsRequired == 1)
1418
{
19+
tapGestureRecognizer = tgr;
1520
return (tgr.Buttons & ButtonsMask.Primary) == ButtonsMask.Primary;
1621
}
1722
}
23+
tapGestureRecognizer = null;
1824
return false;
1925
}
2026
}

src/Core/src/Platform/Android/ViewExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,5 +806,19 @@ void ShowSoftInput()
806806

807807
view.Post(ShowSoftInput);
808808
}
809+
810+
internal static bool IsConfirmKey(this Keycode keyCode)
811+
{
812+
switch (keyCode)
813+
{
814+
case Keycode.DpadCenter:
815+
case Keycode.Enter:
816+
case Keycode.Space:
817+
case Keycode.NumpadEnter:
818+
return true;
819+
default:
820+
return false;
821+
}
822+
}
809823
}
810824
}

0 commit comments

Comments
 (0)