Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

[Android] Handle rapid taps when only single-tap recognizer is present #1188

Merged
merged 2 commits into from
Oct 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla51427.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla59248.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla59580.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla59863_0.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla59863_1.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla59863_2.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ButtonBackgroundColorTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CarouselAsync.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34561.cs" />
Expand Down
24 changes: 22 additions & 2 deletions Xamarin.Forms.Platform.Android/InnerGestureListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,20 @@ bool GestureDetector.IOnDoubleTapListener.OnDoubleTap(MotionEvent e)
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)
Expand Down Expand Up @@ -134,7 +147,7 @@ bool GestureDetector.IOnDoubleTapListener.OnSingleTapConfirmed(MotionEvent e)

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;
}
Expand Down Expand Up @@ -201,5 +214,12 @@ bool HasDoubleTapHandler()
return false;
return _tapGestureRecognizers(2).Any();
}

bool HasSingleTapHandler()
{
if (_tapGestureRecognizers == null)
return false;
return _tapGestureRecognizers(1).Any();
}
}
}