diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla59863_0.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla59863_0.cs new file mode 100644 index 000000000..3b607b17f --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla59863_0.cs @@ -0,0 +1,92 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.Gestures)] +#endif + + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Bugzilla, 59863, "TapGestureRecognizer extremely finicky", PlatformAffected.Android)] + public class Bugzilla59863_0 : TestContentPage + { + int _singleTaps; + const string SingleTapBoxId = "singleTapView"; + + const string Singles = "singles(s)"; + + protected override void Init() + { + var instructions = new Label + { + Text = "Tap the box below several times quickly. " + + "The number displayed below should match the number of times you tap the box." + }; + + var singleTapCounter = new Label {Text = $"{_singleTaps} {Singles}"}; + + var singleTapBox = new BoxView + { + WidthRequest = 100, + HeightRequest = 100, + BackgroundColor = Color.Bisque, + AutomationId = SingleTapBoxId + }; + + var singleTap = new TapGestureRecognizer + { + Command = new Command(() => + { + _singleTaps = _singleTaps + 1; + singleTapCounter.Text = $"{_singleTaps} {Singles} on {SingleTapBoxId}"; + }) + }; + + singleTapBox.GestureRecognizers.Add(singleTap); + + Content = new StackLayout + { + Margin = 40, + HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, + Children = { instructions, singleTapBox, singleTapCounter } + }; + } + +#if UITEST + [Test] + public void TapsCountShouldMatch() + { + // Gonna add this test because we'd want to know if it _did_ start failing + // But it doesn't really help much with this issue; UI test can't tap fast enough to demonstrate the + // problem we're trying to solve + + int tapsToTest = 5; + + RunningApp.WaitForElement(SingleTapBoxId); + + for (int n = 0; n < tapsToTest; n++) + { + RunningApp.Tap(SingleTapBoxId); + } + + RunningApp.WaitForElement($"{tapsToTest} {Singles} on {SingleTapBoxId}"); + } + + [Test] + public void DoubleTapWithOnlySingleTapRecognizerShouldRegisterTwoTaps() + { + RunningApp.WaitForElement(SingleTapBoxId); + RunningApp.DoubleTap(SingleTapBoxId); + + RunningApp.WaitForElement($"2 {Singles} on {SingleTapBoxId}"); + } +#endif + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla59863_1.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla59863_1.cs new file mode 100644 index 000000000..4eb4396a9 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla59863_1.cs @@ -0,0 +1,84 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.Gestures)] +#endif + + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Bugzilla, 59863, "TapGestureRecognizer extremely finicky", PlatformAffected.Android, + issueTestNumber:1)] + public class Bugzilla59863_1 : TestContentPage + { + int _doubleTaps; + const string DoubleTapBoxId = "doubleTapView"; + + const string Doubles = "double(s)"; + + protected override void Init() + { + var instructions = new Label + { + Text = "Tap the box below once. The counter should not increment. " + + "Double tap the box. The counter should increment." + }; + + var doubleTapCounter = new Label {Text = $"{_doubleTaps} {Doubles} on {DoubleTapBoxId}"}; + + var doubleTapBox = new BoxView + { + WidthRequest = 100, + HeightRequest = 100, + BackgroundColor = Color.Chocolate, + AutomationId = DoubleTapBoxId + }; + + var doubleTap = new TapGestureRecognizer + { + NumberOfTapsRequired = 2, + Command = new Command(() => + { + _doubleTaps = _doubleTaps + 1; + doubleTapCounter.Text = $"{_doubleTaps} {Doubles} on {DoubleTapBoxId}"; + }) + }; + + doubleTapBox.GestureRecognizers.Add(doubleTap); + + Content = new StackLayout + { + Margin = 40, + HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, + Children = { instructions, doubleTapBox, doubleTapCounter } + }; + } + +#if UITEST + [Test] + public void SingleTapWithOnlyDoubleTapRecognizerShouldRegisterNothing() + { + RunningApp.WaitForElement(DoubleTapBoxId); + RunningApp.Tap(DoubleTapBoxId); + + RunningApp.WaitForElement($"0 {Doubles} on {DoubleTapBoxId}"); + } + + [Test] + public void DoubleTapWithOnlyDoubleTapRecognizerShouldRegisterOneDoubleTap() + { + RunningApp.WaitForElement(DoubleTapBoxId); + RunningApp.DoubleTap(DoubleTapBoxId); + + RunningApp.WaitForElement($"1 {Doubles} on {DoubleTapBoxId}"); + } +#endif + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla59863_2.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla59863_2.cs new file mode 100644 index 000000000..2513935cd --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla59863_2.cs @@ -0,0 +1,99 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.Gestures)] +#endif + + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Bugzilla, 59863, "TapGestureRecognizer extremely finicky", PlatformAffected.Android, + issueTestNumber: 2)] + public class Bugzilla59863_2 : TestContentPage + { + int _mixedSingleTaps; + int _mixedDoubleTaps; + const string MixedTapBoxId = "mixedTapView"; + + const string Singles = "singles(s)"; + const string Doubles = "double(s)"; + + protected override void Init() + { + var instructions = new Label + { + Text = "Tap the box below once. The single tap counter should increment. " + + "Double tap the box. The double tap counter should increment, " + + "but the single tap counter should not." + }; + + var mixedSingleTapCounter = new Label {Text = $"{_mixedSingleTaps} {Singles}"}; + var mixedDoubleTapCounter = new Label {Text = $"{_mixedDoubleTaps} {Doubles}"}; + + var mixedTapBox = new BoxView + { + WidthRequest = 100, + HeightRequest = 100, + BackgroundColor = Color.Coral, + AutomationId = MixedTapBoxId + }; + + var mixedDoubleTap = new TapGestureRecognizer + { + NumberOfTapsRequired = 2, + Command = new Command(() => + { + _mixedDoubleTaps = _mixedDoubleTaps + 1; + mixedDoubleTapCounter.Text = $"{_mixedDoubleTaps} {Doubles} on {MixedTapBoxId}"; + }) + }; + + var mixedSingleTap = new TapGestureRecognizer + { + NumberOfTapsRequired = 1, + Command = new Command(() => + { + _mixedSingleTaps = _mixedSingleTaps + 1; + mixedSingleTapCounter.Text = $"{_mixedSingleTaps} {Singles} on {MixedTapBoxId}"; + }) + }; + + mixedTapBox.GestureRecognizers.Add(mixedDoubleTap); + mixedTapBox.GestureRecognizers.Add(mixedSingleTap); + + Content = new StackLayout + { + Margin = 40, + HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, + Children = { instructions, mixedTapBox, mixedSingleTapCounter, mixedDoubleTapCounter } + }; + } + +#if UITEST + [Test] + public void DoubleTapWithMixedRecognizersShouldRegisterDoubleTap() + { + RunningApp.WaitForElement(MixedTapBoxId); + RunningApp.DoubleTap(MixedTapBoxId); + + RunningApp.WaitForElement($"1 {Doubles} on {MixedTapBoxId}"); + } + + [Test] + public void SingleTapWithMixedRecognizersShouldRegisterSingleTap() + { + RunningApp.WaitForElement(MixedTapBoxId); + RunningApp.Tap(MixedTapBoxId); + + RunningApp.WaitForElement($"1 {Singles} on {MixedTapBoxId}"); + } +#endif + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index bada851c9..5424e49da 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -222,6 +222,9 @@ + + + diff --git a/Xamarin.Forms.Platform.Android/InnerGestureListener.cs b/Xamarin.Forms.Platform.Android/InnerGestureListener.cs index 99c1d63d2..c529211d5 100644 --- a/Xamarin.Forms.Platform.Android/InnerGestureListener.cs +++ b/Xamarin.Forms.Platform.Android/InnerGestureListener.cs @@ -61,7 +61,20 @@ namespace Xamarin.Forms.Platform.Android if (_disposed) return false; - return _tapDelegate(2); + if (HasDoubleTapHandler()) + { + return _tapDelegate(2); + } + + if (HasSingleTapHandler()) + { + // If we're registering double taps and we don't actually have a double-tap handler, + // but we _do_ have a single-tap handler, then we're really just seeing two singles in a row + // Fire off the delegate for the second single-tap (OnSingleTapUp already did the first one) + return _tapDelegate(1); + } + + return false; } bool GestureDetector.IOnDoubleTapListener.OnDoubleTapEvent(MotionEvent e) @@ -134,7 +147,7 @@ namespace Xamarin.Forms.Platform.Android if (!HasDoubleTapHandler()) { - // We're not worried about double-tap, so OnSingleTap has already run the delegate + // We're not worried about double-tap, so OnSingleTapUp has already run the delegate // there's nothing for us to do here return false; } @@ -201,5 +214,12 @@ namespace Xamarin.Forms.Platform.Android return false; return _tapGestureRecognizers(2).Any(); } + + bool HasSingleTapHandler() + { + if (_tapGestureRecognizers == null) + return false; + return _tapGestureRecognizers(1).Any(); + } } } \ No newline at end of file