summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla24574.cs17
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla25943.cs4
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35477.cs5
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla36703.cs13
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39530.cs4
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39768.cs5
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla44044.cs5
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46363.cs19
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46458.cs2
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla55912.cs5
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla57515.cs147
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla58833.cs131
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/GestureBubblingTests.cs5
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems2
-rw-r--r--Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs120
-rw-r--r--Xamarin.Forms.Platform.Android/FastRenderers/FrameRenderer.cs10
-rw-r--r--Xamarin.Forms.Platform.Android/FastRenderers/GestureManager.cs267
-rw-r--r--Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs15
-rw-r--r--Xamarin.Forms.Platform.Android/FastRenderers/LabelRenderer.cs12
-rw-r--r--Xamarin.Forms.Platform.Android/FastRenderers/VisualElementRenderer.cs4
-rw-r--r--Xamarin.Forms.Platform.Android/GestureManager.cs172
-rw-r--r--Xamarin.Forms.Platform.Android/InnerGestureListener.cs98
-rw-r--r--Xamarin.Forms.Platform.Android/InnerScaleListener.cs20
-rw-r--r--Xamarin.Forms.Platform.Android/PanGestureHandler.cs7
-rw-r--r--Xamarin.Forms.Platform.Android/PinchGestureHandler.cs18
-rw-r--r--Xamarin.Forms.Platform.Android/Platform.cs30
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs16
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs15
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/ViewCellExtensions.cs19
-rw-r--r--Xamarin.Forms.Platform.Android/TapGestureHandler.cs6
-rw-r--r--Xamarin.Forms.Platform.Android/VisualElementExtensions.cs14
-rw-r--r--Xamarin.Forms.Platform.Android/VisualElementPackager.cs1
-rw-r--r--Xamarin.Forms.Platform.Android/VisualElementRenderer.cs180
-rw-r--r--Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj2
34 files changed, 809 insertions, 581 deletions
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla24574.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla24574.cs
index 30916a8c..d476a99c 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla24574.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla24574.cs
@@ -1,30 +1,35 @@
using System;
-
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
#if UITEST
+using Xamarin.Forms.Core.UITests;
using Xamarin.UITest;
using NUnit.Framework;
#endif
namespace Xamarin.Forms.Controls.Issues
{
+#if UITEST
+ [Category(UITestCategories.Gestures)]
+#endif
+
[Preserve (AllMembers = true)]
[Issue (IssueTracker.Bugzilla, 24574, "Tap Double Tap")]
- public class Issue24574 : TestContentPage // or TestMasterDetailPage, etc ...
+ public class Issue24574 : TestContentPage
{
protected override void Init ()
{
var label = new Label {
AutomationId = "TapLabel",
- Text = "123"
+ Text = "123",
+ FontSize = 50
};
- var rec = new TapGestureRecognizer () { NumberOfTapsRequired = 1 };
+ var rec = new TapGestureRecognizer { NumberOfTapsRequired = 1 };
rec.Tapped += (s, e) => { label.Text = "Single"; };
label.GestureRecognizers.Add (rec);
- rec = new TapGestureRecognizer () { NumberOfTapsRequired = 2 };
+ rec = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
rec.Tapped += (s, e) => { label.Text = "Double"; };
label.GestureRecognizers.Add (rec);
@@ -33,7 +38,7 @@ namespace Xamarin.Forms.Controls.Issues
#if UITEST
[Test]
- public void Issue1Test ()
+ public void TapThenDoubleTap ()
{
RunningApp.Screenshot ("I am at Issue 24574");
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla25943.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla25943.cs
index 1b91a36e..cc7664fe 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla25943.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla25943.cs
@@ -11,9 +11,11 @@ namespace Xamarin.Forms.Controls.Issues
{
#if UITEST
[Category(UITestCategories.InputTransparent)]
+ [Category(UITestCategories.Gestures)]
#endif
[Preserve(AllMembers = true)]
- [Issue(IssueTracker.Bugzilla, 25943, "[Android] TapGestureRecognizer does not work with a nested StackLayout", PlatformAffected.Android)]
+ [Issue(IssueTracker.Bugzilla, 25943,
+ "[Android] TapGestureRecognizer does not work with a nested StackLayout", PlatformAffected.Android)]
public class Bugzilla25943 : TestContentPage
{
Label _result;
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35477.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35477.cs
index 5fc92ce7..46493580 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35477.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35477.cs
@@ -3,11 +3,16 @@ using Xamarin.Forms.Internals;
#if UITEST
using NUnit.Framework;
+using Xamarin.Forms.Core.UITests;
using Xamarin.UITest.Queries;
#endif
namespace Xamarin.Forms.Controls.Issues
{
+#if UITEST
+ [Category(UITestCategories.Gestures)]
+#endif
+
[Preserve (AllMembers = true)]
[Issue (IssueTracker.Bugzilla, 35477, "Tapped event does not fire when added to Frame in Android AppCompat",
PlatformAffected.Android)]
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla36703.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla36703.cs
index 47b4d021..0dc3df95 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla36703.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla36703.cs
@@ -4,12 +4,19 @@ 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)]
+ [Category(UITestCategories.IsEnabled)]
+#endif
+
[Preserve(AllMembers = true)]
- [Issue(IssueTracker.Bugzilla, 36703, "TapGestureRecognizer inside initially disable Image will never fire Tapped event", PlatformAffected.All)]
+ [Issue(IssueTracker.Bugzilla, 36703,
+ "TapGestureRecognizer inside initially disable Image will never fire Tapped event", PlatformAffected.All)]
public class Bugzilla36703 : TestContentPage
{
const string TestImage = "testimage";
@@ -22,7 +29,9 @@ namespace Xamarin.Forms.Controls.Issues
var image = new Image { Source = "coffee.png", IsEnabled = false, AutomationId = TestImage };
var button = new Button { Text = $"Toggle IsEnabled (now {image.IsEnabled})", AutomationId = Toggle };
var resultLabel = new Label { Text = "Testing..."};
- var instructions = new Label { Text = $"Tap the image. The '{Testing}' label should remain unchanged. Tap the 'Toggle IsEnabled' button. Now tap the image again. The {Testing} Label should change its text to {Success}." };
+ var instructions = new Label { Text = $"Tap the image. The '{Testing}' label should remain unchanged. "
+ + $"Tap the 'Toggle IsEnabled' button. Now tap the image again."
+ + $" The {Testing} Label should change its text to {Success}." };
button.Clicked += (sender, args) =>
{
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39530.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39530.cs
index 6b7d380a..cc17290e 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39530.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39530.cs
@@ -9,6 +9,10 @@ using Xamarin.Forms.Core.UITests;
namespace Xamarin.Forms.Controls.Issues
{
+#if UITEST
+ [Category(UITestCategories.Gestures)]
+#endif
+
[Preserve (AllMembers = true)]
[Issue (IssueTracker.Bugzilla, 39530, "Frames do not handle pan or pinch gestures under AppCompat", PlatformAffected.Android)]
public class Bugzilla39530 : TestContentPage
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39768.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39768.cs
index ae365782..05e88ed5 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39768.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39768.cs
@@ -5,10 +5,15 @@ using System;
#if UITEST
using Xamarin.UITest;
using NUnit.Framework;
+using Xamarin.Forms.Core.UITests;
#endif
namespace Xamarin.Forms.Controls
{
+#if UITEST
+ [Category(UITestCategories.Gestures)]
+#endif
+
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Bugzilla, 39768, "PanGestureRecognizer sometimes won't fire completed event when dragging very slowly")]
public class Bugzilla39768 : TestContentPage
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla44044.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla44044.cs
index 22f8b7bc..8d8b08c1 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla44044.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla44044.cs
@@ -6,10 +6,15 @@ using Xamarin.Forms.PlatformConfiguration;
#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, 44044, "TabbedPage steals swipe gestures", PlatformAffected.Android)]
public class Bugzilla44044 : TestTabbedPage
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46363.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46363.cs
index 708968d8..91b7a950 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46363.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46363.cs
@@ -10,6 +10,12 @@ using NUnit.Framework;
namespace Xamarin.Forms.Controls.Issues
{
+#if UITEST
+ [Category(UITestCategories.Gestures)]
+ [Category(UITestCategories.ListView)]
+ [Category(UITestCategories.Cells)]
+#endif
+
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Bugzilla, 46363, "TapGestureRecognizer blocks List View Context Actions", PlatformAffected.Android)]
public class Bugzilla46363 : TestContentPage
@@ -17,6 +23,7 @@ namespace Xamarin.Forms.Controls.Issues
const string Target = "Two";
const string ContextAction = "Context Action";
const string TapSuccess = "Tap Success";
+ const string TapFailure = "Tap command executed more than once";
const string ContextSuccess = "Context Menu Success";
const string Testing = "Testing";
@@ -25,7 +32,7 @@ namespace Xamarin.Forms.Controls.Issues
protected override void Init()
{
- var list = new List<string> { "One", Target, "Two", "Three" };
+ var list = new List<string> { "One", Target, "Three", "Four" };
var lv = new ListView
{
@@ -38,7 +45,15 @@ namespace Xamarin.Forms.Controls.Issues
s_tapCommand = new Command(() =>
{
- result.Text = TapSuccess;
+ if (result.Text == TapSuccess || result.Text == TapFailure)
+ {
+ // We want this test to fail if the tap command is executed more than once
+ result.Text = TapFailure;
+ }
+ else
+ {
+ result.Text = TapSuccess;
+ }
});
s_contextCommand = new Command(() =>
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46458.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46458.cs
index 8863770f..2fde0e0d 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46458.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla46458.cs
@@ -98,7 +98,7 @@ namespace Xamarin.Forms.Controls.Issues
#if UITEST
[Test]
- public void Issue1Test()
+ public void GridIsEnabled()
{
RunningApp.WaitForElement(q => q.Marked("entry"));
RunningApp.Tap(q => q.Marked("entry"));
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla55912.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla55912.cs
index b5a65d88..0a2ee05b 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla55912.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla55912.cs
@@ -4,11 +4,16 @@ using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
#if 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, 55912, "Tap event not always propagated to containing Grid/StackLayout",
PlatformAffected.Android)]
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla57515.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla57515.cs
new file mode 100644
index 00000000..b9f3c3ef
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla57515.cs
@@ -0,0 +1,147 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System;
+#if UITEST
+using Xamarin.UITest;
+using Xamarin.Forms.Core.UITests;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.Gestures)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Bugzilla, 57515, "PinchGestureRecognizer not getting called on Android ", PlatformAffected.Android)]
+ public class Bugzilla57515 : TestContentPage
+ {
+ const string ZoomImage = "zoomImg";
+ const string ZoomContainer = "zoomContainer";
+
+ protected override void Init()
+ {
+ var layout = new Grid
+ {
+ RowDefinitions = new RowDefinitionCollection
+ {
+ new RowDefinition { Height = 80 },
+ new RowDefinition { Height = GridLength.Star }
+ }
+ };
+
+ var scaleLabel = new Label();
+ layout.Children.Add(scaleLabel);
+
+ var pinchToZoomContainer = new PinchToZoomContainer
+ {
+ Margin = new Thickness(80),
+ AutomationId = ZoomContainer,
+ Content = new Image
+ {
+ AutomationId = ZoomImage,
+ Source = ImageSource.FromFile("oasis.jpg")
+ }
+ };
+
+ Grid.SetRow(pinchToZoomContainer, 1);
+ layout.Children.Add(pinchToZoomContainer);
+
+ scaleLabel.BindingContext = pinchToZoomContainer;
+ scaleLabel.SetBinding(Label.TextProperty, new Binding("CurrentScale"));
+
+ Content = layout;
+ }
+
+ class PinchToZoomContainer : ContentView
+ {
+ public static readonly BindableProperty CurrentScaleProperty =
+ BindableProperty.Create("CurrentScale", typeof(double), typeof(PinchToZoomContainer), 1.0);
+
+ public double CurrentScale
+ {
+ get { return (double)GetValue(CurrentScaleProperty); }
+ set { SetValue(CurrentScaleProperty, value); }
+ }
+
+ double startScale = 1;
+ double xOffset = 0;
+ double yOffset = 0;
+
+ public PinchToZoomContainer()
+ {
+ var pinchGesture = new PinchGestureRecognizer();
+ pinchGesture.PinchUpdated += OnPinchUpdated;
+ GestureRecognizers.Add(pinchGesture);
+ }
+
+ void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
+ {
+ if (e.Status == GestureStatus.Started)
+ {
+ // Store the current scale factor applied to the wrapped user interface element,
+ // and zero the components for the center point of the translate transform.
+ startScale = Content.Scale;
+ Content.AnchorX = 0;
+ Content.AnchorY = 0;
+ }
+ if (e.Status == GestureStatus.Running)
+ {
+ // Calculate the scale factor to be applied.
+ CurrentScale += (e.Scale - 1) * startScale;
+ CurrentScale = Math.Max(1, CurrentScale);
+
+ // The ScaleOrigin is in relative coordinates to the wrapped user interface element,
+ // so get the X pixel coordinate.
+ double renderedX = Content.X + xOffset;
+ double deltaX = renderedX / Width;
+ double deltaWidth = Width / (Content.Width * startScale);
+ double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
+
+ // The ScaleOrigin is in relative coordinates to the wrapped user interface element,
+ // so get the Y pixel coordinate.
+ double renderedY = Content.Y + yOffset;
+ double deltaY = renderedY / Height;
+ double deltaHeight = Height / (Content.Height * startScale);
+ double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
+
+ // Calculate the transformed element pixel coordinates.
+ double targetX = xOffset - (originX * Content.Width) * (CurrentScale - startScale);
+ double targetY = yOffset - (originY * Content.Height) * (CurrentScale - startScale);
+
+ // Apply translation based on the change in origin.
+ Content.TranslationX = targetX.Clamp(-Content.Width * (CurrentScale - 1), 0);
+ Content.TranslationY = targetY.Clamp(-Content.Height * (CurrentScale - 1), 0);
+
+ // Apply scale factor
+ Content.Scale = CurrentScale;
+ }
+ if (e.Status == GestureStatus.Completed)
+ {
+ // Store the translation delta's of the wrapped user interface element.
+ xOffset = Content.TranslationX;
+ yOffset = Content.TranslationY;
+ }
+ }
+ }
+
+#if UITEST
+ [Test]
+ public void Bugzilla57515Test()
+ {
+ RunningApp.WaitForElement(ZoomContainer);
+ RunningApp.WaitForElement("1");
+ RunningApp.PinchToZoomIn(ZoomContainer);
+ RunningApp.WaitForNoElement("1"); // The scale should have changed during the zoom
+ }
+#endif
+ }
+
+ public static class DoubleExtensions
+ {
+ public static double Clamp(this double self, double min, double max)
+ {
+ return Math.Min(max, Math.Max(self, min));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla58833.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla58833.cs
new file mode 100644
index 00000000..9979227a
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla58833.cs
@@ -0,0 +1,131 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.Gestures)]
+ [Category(UITestCategories.ListView)]
+ [Category(UITestCategories.Cells)]
+#endif
+
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Bugzilla, 58833, "ListView SelectedItem Binding does not fire", PlatformAffected.Android)]
+ public class Bugzilla58833 : TestContentPage
+ {
+ const string ItemSelectedSuccess = "ItemSelected Success";
+ const string TapGestureSucess = "TapGesture Fired";
+ Label _resultLabel;
+ static Label s_tapGestureFired;
+
+ [Preserve(AllMembers = true)]
+ class TestCell : ViewCell
+ {
+ readonly Label _content;
+
+ internal static int s_index;
+
+ public TestCell()
+ {
+ _content = new Label();
+
+ if (s_index % 2 == 0)
+ {
+ _content.GestureRecognizers.Add(new TapGestureRecognizer
+ {
+ Command = new Command(() =>
+ {
+ s_tapGestureFired.Text = TapGestureSucess;
+ })
+ });
+ }
+
+ View = _content;
+ ContextActions.Add(new MenuItem { Text = s_index++ + " Action" });
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ _content.Text = (string)BindingContext;
+ }
+ }
+
+ protected override void Init()
+ {
+ TestCell.s_index = 0;
+
+ _resultLabel = new Label { Text = "Testing..." };
+ s_tapGestureFired = new Label { Text = "Testing..." };
+
+ var items = new List<string>();
+ for (int i = 0; i < 5; i++)
+ items.Add($"Item #{i}");
+
+ var list = new ListView
+ {
+ ItemTemplate = new DataTemplate(typeof(TestCell)),
+ ItemsSource = items
+ };
+ list.ItemSelected += List_ItemSelected;
+
+ Content = new StackLayout
+ {
+ Children = {
+ _resultLabel,
+ s_tapGestureFired,
+ list
+ }
+ };
+ }
+
+ void List_ItemSelected(object sender, SelectedItemChangedEventArgs e)
+ {
+ _resultLabel.Text = ItemSelectedSuccess;
+ }
+
+#if UITEST
+ protected override bool Isolate => true;
+
+ [Test]
+ public void Bugzilla58833Test()
+ {
+ // Item #1 should not have a tap gesture, so it should be selectable
+ RunningApp.WaitForElement(q => q.Marked("Item #1"));
+ RunningApp.Tap(q => q.Marked("Item #1"));
+ RunningApp.WaitForElement(q => q.Marked(ItemSelectedSuccess));
+
+ // Item #2 should have a tap gesture
+ RunningApp.WaitForElement(q => q.Marked("Item #2"));
+ RunningApp.Tap(q => q.Marked("Item #2"));
+ RunningApp.WaitForElement(q => q.Marked(TapGestureSucess));
+
+ // Both items should allow access to the context menu
+ RunningApp.ActivateContextMenu("Item #2");
+ RunningApp.WaitForElement("2 Action");
+#if __ANDROID__
+ RunningApp.Back();
+#else
+ RunningApp.Tap(q => q.Marked("Item #3"));
+#endif
+
+ RunningApp.ActivateContextMenu("Item #1");
+ RunningApp.WaitForElement("1 Action");
+#if __ANDROID__
+ RunningApp.Back();
+#else
+ RunningApp.Tap(q => q.Marked("Item #3"));
+#endif
+
+ }
+#endif
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/GestureBubblingTests.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/GestureBubblingTests.cs
index 145fbf80..38736211 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/GestureBubblingTests.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/GestureBubblingTests.cs
@@ -8,6 +8,7 @@ using Xamarin.Forms.Internals;
#if UITEST
using NUnit.Framework;
using Xamarin.UITest.Queries;
+using Xamarin.Forms.Core.UITests;
#endif
namespace Xamarin.Forms.Controls.Issues
@@ -17,6 +18,10 @@ namespace Xamarin.Forms.Controls.Issues
// will trigger the frame's tap gesture; for most controls it will not (the control itself absorbs the tap),
// but for non-interactive controls (box, frame, image, label) the gesture bubbles up to the container.
+#if UITEST
+ [Category(UITestCategories.Gestures)]
+#endif
+
[Preserve(AllMembers = true)]
[Issue(IssueTracker.None, 00100100, "Verify that the tap gesture bubbling behavior is consistent across the platforms", PlatformAffected.All)]
public class GestureBubblingTests : TestNavigationPage
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 6b128ca8..6d25e7eb 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
@@ -213,9 +213,11 @@
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla55912.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla57317.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla57114.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla57515.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla57758.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla57910.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla58406.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla58833.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla59248.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ButtonBackgroundColorTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CarouselAsync.cs" />
diff --git a/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs b/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs
index 75246220..44f2bbbc 100644
--- a/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs
@@ -3,6 +3,8 @@ using Android.Views;
using AView = Android.Views.View;
using Xamarin.Forms.Internals;
using System;
+using System.Linq;
+using Android.Runtime;
namespace Xamarin.Forms.Platform.Android
{
@@ -53,8 +55,48 @@ namespace Xamarin.Forms.Platform.Android
readonly BindableProperty _unevenRows;
IVisualElementRenderer _view;
ViewCell _viewCell;
+ GestureDetector _longPressGestureDetector;
+ ListViewRenderer _listViewRenderer;
+ bool _watchForLongPress;
- public ViewCellContainer(Context context, IVisualElementRenderer view, ViewCell viewCell, View parent, BindableProperty unevenRows, BindableProperty rowHeight) : base(context)
+ ListViewRenderer ListViewRenderer
+ {
+ get
+ {
+ if (_listViewRenderer != null)
+ {
+ return _listViewRenderer;
+ }
+
+ var listView = _parent as ListView;
+
+ if (listView == null)
+ {
+ return null;
+ }
+
+ _listViewRenderer = Platform.GetRenderer(listView) as ListViewRenderer;
+
+ return _listViewRenderer;
+ }
+ }
+
+ GestureDetector LongPressGestureDetector
+ {
+ get
+ {
+ if (_longPressGestureDetector != null)
+ {
+ return _longPressGestureDetector;
+ }
+
+ _longPressGestureDetector = new GestureDetector(new LongPressGestureListener(TriggerLongClick));
+ return _longPressGestureDetector;
+ }
+ }
+
+ public ViewCellContainer(Context context, IVisualElementRenderer view, ViewCell viewCell, View parent,
+ BindableProperty unevenRows, BindableProperty rowHeight) : base(context)
{
_view = view;
_parent = parent;
@@ -63,7 +105,7 @@ namespace Xamarin.Forms.Platform.Android
_viewCell = viewCell;
AddView(view.View);
UpdateIsEnabled();
- UpdateLongClickable();
+ UpdateWatchForLongPress();
}
protected bool ParentHasUnevenRows
@@ -86,6 +128,11 @@ namespace Xamarin.Forms.Platform.Android
if (!Enabled)
return true;
+ if (_watchForLongPress)
+ {
+ LongPressGestureDetector.OnTouchEvent(ev);
+ }
+
return base.OnInterceptTouchEvent(ev);
}
@@ -137,7 +184,7 @@ namespace Xamarin.Forms.Platform.Android
AddView(_view.View);
UpdateIsEnabled();
- UpdateLongClickable();
+ UpdateWatchForLongPress();
Performance.Stop();
}
@@ -182,12 +229,69 @@ namespace Xamarin.Forms.Platform.Android
Performance.Stop();
}
- void UpdateLongClickable()
+ void UpdateWatchForLongPress()
{
- // In order for context menu long presses/clicks to work on ViewCells which have
- // and Clickable content, we have to make the container view LongClickable
- // If we don't have a context menu, we don't have to worry about it
- _view.View.LongClickable = _viewCell.ContextActions.Count > 0;
+ var vw = _view.Element as Xamarin.Forms.View;
+ if (vw == null)
+ {
+ return;
+ }
+
+ // If the view cell has any context actions and the View itself has any Tap Gestures, they're going
+ // to conflict with one another - the Tap Gesture handling will prevent the ListViewAdapter's
+ // LongClick handling from happening. So we need to watch locally for LongPress and if we see it,
+ // trigger the LongClick manually.
+ _watchForLongPress = _viewCell.ContextActions.Count > 0
+ && vw.GestureRecognizers.Any(t => t is TapGestureRecognizer);
+ }
+
+ void TriggerLongClick()
+ {
+ ListViewRenderer?.LongClickOn(this);
+ }
+
+ internal class LongPressGestureListener : Java.Lang.Object, GestureDetector.IOnGestureListener
+ {
+ readonly Action _onLongClick;
+
+ internal LongPressGestureListener(Action onLongClick)
+ {
+ _onLongClick = onLongClick;
+ }
+
+ internal LongPressGestureListener(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership)
+ {
+ }
+
+ public bool OnDown(MotionEvent e)
+ {
+ return true;
+ }
+
+ public bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
+ {
+ return false;
+ }
+
+ public void OnLongPress(MotionEvent e)
+ {
+ _onLongClick();
+ }
+
+ public bool OnScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
+ {
+ return false;
+ }
+
+ public void OnShowPress(MotionEvent e)
+ {
+
+ }
+
+ public bool OnSingleTapUp(MotionEvent e)
+ {
+ return false;
+ }
}
}
}
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/FrameRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/FrameRenderer.cs
index 3239715d..0adaf858 100644
--- a/Xamarin.Forms.Platform.Android/FastRenderers/FrameRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/FastRenderers/FrameRenderer.cs
@@ -23,6 +23,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
readonly GestureManager _gestureManager;
readonly EffectControlProvider _effectControlProvider;
+ readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
@@ -68,6 +69,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
if (frame == null)
throw new ArgumentException("Element must be of type Frame");
Element = frame;
+ _motionEventHelper.UpdateElement(frame);
if (!string.IsNullOrEmpty(Element.AutomationId))
ContentDescription = Element.AutomationId;
@@ -182,10 +184,12 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
public override bool OnTouchEvent(MotionEvent e)
{
- bool handled;
- var result = _gestureManager.OnTouchEvent(e, Parent, out handled);
+ if (_gestureManager.OnTouchEvent(e))
+ {
+ return true;
+ }
- return handled ? result : base.OnTouchEvent(e);
+ return _motionEventHelper.HandleMotionEvent(Parent, e);
}
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/GestureManager.cs b/Xamarin.Forms.Platform.Android/FastRenderers/GestureManager.cs
deleted file mode 100644
index ec101383..00000000
--- a/Xamarin.Forms.Platform.Android/FastRenderers/GestureManager.cs
+++ /dev/null
@@ -1,267 +0,0 @@
-using System;
-using System.Collections.ObjectModel;
-using System.Collections.Specialized;
-using System.ComponentModel;
-using Android.Support.V4.View;
-using Android.Views;
-using Object = Java.Lang.Object;
-
-namespace Xamarin.Forms.Platform.Android.FastRenderers
-{
- internal class GestureManager : Object, global::Android.Views.View.IOnClickListener, global::Android.Views.View.IOnTouchListener
- {
- IVisualElementRenderer _renderer;
- readonly Lazy<GestureDetector> _gestureDetector;
- readonly PanGestureHandler _panGestureHandler;
- readonly PinchGestureHandler _pinchGestureHandler;
- readonly Lazy<ScaleGestureDetector> _scaleDetector;
- readonly TapGestureHandler _tapGestureHandler;
- readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
- InnerGestureListener _gestureListener;
-
- bool _clickable;
- bool _disposed;
- bool _inputTransparent;
- bool _isEnabled;
-
- NotifyCollectionChangedEventHandler _collectionChangeHandler;
-
- VisualElement Element => _renderer?.Element;
-
- View View => _renderer?.Element as View;
-
- global::Android.Views.View Control => _renderer?.View;
-
- public GestureManager(IVisualElementRenderer renderer)
- {
- _renderer = renderer;
- _renderer.ElementChanged += OnElementChanged;
-
- _tapGestureHandler = new TapGestureHandler(() => View);
- _panGestureHandler = new PanGestureHandler(() => View, Control.Context.FromPixels);
- _pinchGestureHandler = new PinchGestureHandler(() => View);
- _gestureDetector =
- new Lazy<GestureDetector>(
- () =>
- new GestureDetector(
- _gestureListener =
- new InnerGestureListener(_tapGestureHandler.OnTap, _tapGestureHandler.TapGestureRecognizers,
- _panGestureHandler.OnPan, _panGestureHandler.OnPanStarted, _panGestureHandler.OnPanComplete)));
-
- _scaleDetector =
- new Lazy<ScaleGestureDetector>(
- () =>
- new ScaleGestureDetector(Control.Context,
- new InnerScaleListener(_pinchGestureHandler.OnPinch, _pinchGestureHandler.OnPinchStarted,
- _pinchGestureHandler.OnPinchEnded), Control.Handler));
-
- Control.SetOnClickListener(this);
- Control.SetOnTouchListener(this);
- }
-
- public bool OnTouchEvent(MotionEvent e, IViewParent parent, out bool handled)
- {
- if (_inputTransparent)
- {
- handled = true;
- return false;
- }
-
- if (View.GestureRecognizers.Count == 0)
- {
- handled = true;
- return _motionEventHelper.HandleMotionEvent(parent, e);
- }
-
- handled = false;
- return false;
- }
-
- void OnElementChanged(object sender, VisualElementChangedEventArgs e)
- {
- if (e.OldElement != null)
- {
- UnsubscribeGestureRecognizers(e.OldElement);
- e.OldElement.PropertyChanged -= OnElementPropertyChanged;
- }
-
- if (e.NewElement != null)
- {
- UpdateGestureRecognizers(true);
- SubscribeGestureRecognizers(e.NewElement);
- _motionEventHelper.UpdateElement(e.NewElement);
- e.NewElement.PropertyChanged += OnElementPropertyChanged;
- }
-
- UpdateInputTransparent();
- UpdateIsEnabled();
- }
-
- void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
- UpdateInputTransparent();
- else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
- UpdateIsEnabled();
- }
-
- protected override void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- _disposed = true;
-
- if (disposing)
- {
- if (Element != null)
- {
- Element.PropertyChanged -= OnElementPropertyChanged;
- }
-
- Control.SetOnClickListener(null);
- Control.SetOnTouchListener(null);
-
- if (_gestureListener != null)
- {
- _gestureListener.Dispose();
- _gestureListener = null;
- }
-
- if (_renderer?.Element != null)
- {
- UnsubscribeGestureRecognizers(Element);
- }
-
- _renderer = null;
- }
-
- base.Dispose(disposing);
- }
-
- void global::Android.Views.View.IOnClickListener.OnClick(global::Android.Views.View v)
- {
- _tapGestureHandler.OnSingleClick();
- }
-
- bool global::Android.Views.View.IOnTouchListener.OnTouch(global::Android.Views.View v, MotionEvent e)
- {
- if (!_isEnabled)
- return true;
-
- if (_inputTransparent)
- return false;
-
- var handled = false;
- if (_pinchGestureHandler.IsPinchSupported)
- {
- if (!_scaleDetector.IsValueCreated)
- ScaleGestureDetectorCompat.SetQuickScaleEnabled(_scaleDetector.Value, true);
- handled = _scaleDetector.Value.OnTouchEvent(e);
- }
-
- if (_gestureDetector.IsValueCreated && _gestureDetector.Value.Handle == IntPtr.Zero)
- {
- // This gesture detector has already been disposed, probably because it's on a cell which is going away
- return handled;
- }
-
- // It's very important that the gesture detection happen first here
- // if we check handled first, we might short-circuit and never check for tap/pan
- handled = _gestureDetector.Value.OnTouchEvent(e) || handled;
-
- v.EnsureLongClickCancellation(e, handled, Element);
-
- return handled;
- }
-
- void HandleGestureRecognizerCollectionChanged(object sender,
- NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
- {
- UpdateGestureRecognizers();
- }
-
- void SubscribeGestureRecognizers(VisualElement element)
- {
- var view = element as View;
- if (view == null)
- {
- return;
- }
-
- if (_collectionChangeHandler == null)
- {
- _collectionChangeHandler = HandleGestureRecognizerCollectionChanged;
- }
-
- var observableCollection = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers;
- if (observableCollection != null)
- {
- observableCollection.CollectionChanged += _collectionChangeHandler;
- }
- }
-
- void UnsubscribeGestureRecognizers(VisualElement element)
- {
- var view = element as View;
- if (view == null || _collectionChangeHandler == null)
- {
- return;
- }
-
- var observableCollection = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers;
- if (observableCollection != null)
- {
- observableCollection.CollectionChanged -= _collectionChangeHandler;
- }
- }
-
- void UpdateClickable(bool force = false)
- {
- var view = Element as View;
- if (view == null)
- {
- return;
- }
-
- bool newValue = view.ShouldBeMadeClickable();
- if (force || _clickable != newValue)
- {
- Control.Clickable = newValue;
- _clickable = newValue;
- }
- }
-
- void UpdateGestureRecognizers(bool forceClick = false)
- {
- if (Element == null)
- {
- return;
- }
-
- UpdateClickable(forceClick);
- }
-
- void UpdateInputTransparent()
- {
- if (Element == null)
- {
- return;
- }
-
- _inputTransparent = Element.InputTransparent;
- }
-
- void UpdateIsEnabled()
- {
- if (Element == null)
- {
- return;
- }
-
- _isEnabled = Element.IsEnabled;
- }
- }
-} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs
index 24623b27..6e152350 100644
--- a/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs
@@ -16,6 +16,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
int? _defaultLabelFor;
VisualElementTracker _visualElementTracker;
VisualElementRenderer _visualElementRenderer;
+ readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
protected override void Dispose(bool disposing)
{
@@ -71,12 +72,14 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
}
public override bool OnTouchEvent(MotionEvent e)
- {
- bool handled;
- var result = _visualElementRenderer.OnTouchEvent(e, Parent, out handled);
+ {
+ if (_visualElementRenderer.OnTouchEvent(e))
+ {
+ return true;
+ }
- return handled ? result : base.OnTouchEvent(e);
- }
+ return _motionEventHelper.HandleMotionEvent(Parent, e);
+ }
Size MinimumSize()
{
@@ -122,7 +125,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
}
Internals.Performance.Stop();
-
+ _motionEventHelper.UpdateElement(element);
OnElementChanged(new ElementChangedEventArgs<Image>(oldElement, _element));
_element?.SendViewInitialized(Control);
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/LabelRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/LabelRenderer.cs
index f92873b4..bcf83987 100644
--- a/Xamarin.Forms.Platform.Android/FastRenderers/LabelRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/FastRenderers/LabelRenderer.cs
@@ -23,7 +23,8 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
Color _lastUpdateColor = Color.Default;
VisualElementTracker _visualElementTracker;
VisualElementRenderer _visualElementRenderer;
-
+ readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
+
bool _wasFormatted;
public LabelRenderer() : base(Forms.Context)
@@ -112,6 +113,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
throw new ArgumentException("Element must be of type Label");
Element = label;
+ _motionEventHelper.UpdateElement(element);
}
void IVisualElementRenderer.SetLabelFor(int? id)
@@ -163,10 +165,12 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
public override bool OnTouchEvent(MotionEvent e)
{
- bool handled;
- var result = _visualElementRenderer.OnTouchEvent(e, Parent, out handled);
+ if (_visualElementRenderer.OnTouchEvent(e))
+ {
+ return true;
+ }
- return handled ? result : base.OnTouchEvent(e);
+ return _motionEventHelper.HandleMotionEvent(Parent, e);
}
void OnElementChanged(ElementChangedEventArgs<Label> e)
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/VisualElementRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/VisualElementRenderer.cs
index 91943fb0..c6967688 100644
--- a/Xamarin.Forms.Platform.Android/FastRenderers/VisualElementRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/FastRenderers/VisualElementRenderer.cs
@@ -45,9 +45,9 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
Control.SetBackgroundColor((color ?? Element.BackgroundColor).ToAndroid());
}
- public bool OnTouchEvent(MotionEvent e, IViewParent parent, out bool handled)
+ public bool OnTouchEvent(MotionEvent e)
{
- return _gestureManager.OnTouchEvent(e, parent, out handled);
+ return _gestureManager.OnTouchEvent(e);
}
public void Dispose()
diff --git a/Xamarin.Forms.Platform.Android/GestureManager.cs b/Xamarin.Forms.Platform.Android/GestureManager.cs
new file mode 100644
index 00000000..62d5db64
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/GestureManager.cs
@@ -0,0 +1,172 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using Android.Support.V4.View;
+using Android.Views;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ internal class GestureManager : IDisposable
+ {
+ IVisualElementRenderer _renderer;
+ readonly Lazy<GestureDetector> _tapAndPanDetector;
+ readonly Lazy<ScaleGestureDetector> _scaleDetector;
+
+ bool _disposed;
+ bool _inputTransparent;
+ bool _isEnabled;
+
+ VisualElement Element => _renderer?.Element;
+
+ View View => _renderer?.Element as View;
+
+ global::Android.Views.View Control => _renderer?.View;
+
+ public GestureManager(IVisualElementRenderer renderer)
+ {
+ _renderer = renderer;
+ _renderer.ElementChanged += OnElementChanged;
+
+ _tapAndPanDetector = new Lazy<GestureDetector>(InitializeTapAndPanDetector);
+ _scaleDetector = new Lazy<ScaleGestureDetector>(InitializeScaleDetector);
+ }
+
+ public bool OnTouchEvent(MotionEvent e)
+ {
+ if (Control == null)
+ {
+ return false;
+ }
+
+ if (!_isEnabled || _inputTransparent)
+ {
+ return false;
+ }
+
+ if (!DetectorsValid())
+ {
+ return false;
+ }
+
+ var eventConsumed = false;
+ if (ViewHasPinchGestures())
+ {
+ eventConsumed = _scaleDetector.Value.OnTouchEvent(e);
+ }
+
+ eventConsumed = _tapAndPanDetector.Value.OnTouchEvent(e) || eventConsumed;
+
+ return eventConsumed;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ bool DetectorsValid()
+ {
+ // Make sure we're not testing for gestures on old motion events after our
+ // detectors have already been disposed
+
+ if (_scaleDetector.IsValueCreated && _scaleDetector.Value.Handle == IntPtr.Zero)
+ {
+ return false;
+ }
+
+ if (_tapAndPanDetector.IsValueCreated && _tapAndPanDetector.Value.Handle == IntPtr.Zero)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ GestureDetector InitializeTapAndPanDetector()
+ {
+ var listener = new InnerGestureListener(new TapGestureHandler(() => View),
+ new PanGestureHandler(() => View, Control.Context.FromPixels));
+
+ return new GestureDetector(listener);
+ }
+
+ ScaleGestureDetector InitializeScaleDetector()
+ {
+ var listener = new InnerScaleListener(new PinchGestureHandler(() => View));
+ var detector = new ScaleGestureDetector(Control.Context, listener, Control.Handler);
+ ScaleGestureDetectorCompat.SetQuickScaleEnabled(detector, true);
+
+ return detector;
+ }
+
+ bool ViewHasPinchGestures()
+ {
+ return View != null && View.GestureRecognizers.OfType<PinchGestureRecognizer>().Any();
+ }
+
+ void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+ {
+ if (e.OldElement != null)
+ {
+ e.OldElement.PropertyChanged -= OnElementPropertyChanged;
+ }
+
+ if (e.NewElement != null)
+ {
+ e.NewElement.PropertyChanged += OnElementPropertyChanged;
+ }
+
+ UpdateInputTransparent();
+ UpdateIsEnabled();
+ }
+
+ void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
+ UpdateInputTransparent();
+ else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled();
+ }
+
+ protected void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ if (disposing)
+ {
+ if (Element != null)
+ {
+ Element.PropertyChanged -= OnElementPropertyChanged;
+ }
+
+ _renderer = null;
+ }
+ }
+
+ void UpdateInputTransparent()
+ {
+ if (Element == null)
+ {
+ return;
+ }
+
+ _inputTransparent = Element.InputTransparent;
+ }
+
+ void UpdateIsEnabled()
+ {
+ if (Element == null)
+ {
+ return;
+ }
+
+ _isEnabled = Element.IsEnabled;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/InnerGestureListener.cs b/Xamarin.Forms.Platform.Android/InnerGestureListener.cs
index 928c9c13..99c1d63d 100644
--- a/Xamarin.Forms.Platform.Android/InnerGestureListener.cs
+++ b/Xamarin.Forms.Platform.Android/InnerGestureListener.cs
@@ -9,6 +9,8 @@ namespace Xamarin.Forms.Platform.Android
{
internal class InnerGestureListener : Object, GestureDetector.IOnGestureListener, GestureDetector.IOnDoubleTapListener
{
+ readonly TapGestureHandler _tapGestureHandler;
+ readonly PanGestureHandler _panGestureHandler;
bool _isScrolling;
float _lastX;
float _lastY;
@@ -20,25 +22,31 @@ namespace Xamarin.Forms.Platform.Android
Func<int, bool> _tapDelegate;
Func<int, IEnumerable<TapGestureRecognizer>> _tapGestureRecognizers;
- public InnerGestureListener(Func<int, bool> tapDelegate, Func<int, IEnumerable<TapGestureRecognizer>> tapGestureRecognizers, Func<float, float, int, bool> scrollDelegate,
- Func<int, bool> scrollStartedDelegate, Func<bool> scrollCompleteDelegate)
+ public InnerGestureListener(TapGestureHandler tapGestureHandler, PanGestureHandler panGestureHandler)
{
- if (tapDelegate == null)
- throw new ArgumentNullException(nameof(tapDelegate));
- if (tapGestureRecognizers == null)
- throw new ArgumentNullException(nameof(tapGestureRecognizers));
- if (scrollDelegate == null)
- throw new ArgumentNullException(nameof(scrollDelegate));
- if (scrollStartedDelegate == null)
- throw new ArgumentNullException(nameof(scrollStartedDelegate));
- if (scrollCompleteDelegate == null)
- throw new ArgumentNullException(nameof(scrollCompleteDelegate));
+ if (tapGestureHandler == null)
+ {
+ throw new ArgumentNullException(nameof(tapGestureHandler));
+ }
+
+ if (panGestureHandler == null)
+ {
+ throw new ArgumentNullException(nameof(panGestureHandler));
+ }
+
+ _tapGestureHandler = tapGestureHandler;
+ _panGestureHandler = panGestureHandler;
- _tapDelegate = tapDelegate;
- _tapGestureRecognizers = tapGestureRecognizers;
- _scrollDelegate = scrollDelegate;
- _scrollStartedDelegate = scrollStartedDelegate;
- _scrollCompleteDelegate = scrollCompleteDelegate;
+ _tapDelegate = tapGestureHandler.OnTap;
+ _tapGestureRecognizers = tapGestureHandler.TapGestureRecognizers;
+ _scrollDelegate = panGestureHandler.OnPan;
+ _scrollStartedDelegate = panGestureHandler.OnPanStarted;
+ _scrollCompleteDelegate = panGestureHandler.OnPanComplete;
+ }
+
+ bool HasAnyGestures()
+ {
+ return _panGestureHandler.HasAnyGestures() || _tapGestureHandler.HasAnyGestures();
}
// This is needed because GestureRecognizer callbacks can be delayed several hundred milliseconds
@@ -48,16 +56,6 @@ namespace Xamarin.Forms.Platform.Android
{
}
- internal void OnTouchEvent(MotionEvent e)
- {
- if (e.Action == MotionEventActions.Up)
- EndScrolling();
- else if (e.Action == MotionEventActions.Down)
- SetStartingPosition(e);
- else if (e.Action == MotionEventActions.Move)
- StartScrolling(e);
- }
-
bool GestureDetector.IOnDoubleTapListener.OnDoubleTap(MotionEvent e)
{
if (_disposed)
@@ -71,21 +69,19 @@ namespace Xamarin.Forms.Platform.Android
return false;
}
- bool GestureDetector.IOnDoubleTapListener.OnSingleTapConfirmed(MotionEvent e)
- {
- if (_disposed)
- return false;
-
- // optimization: only wait for a second tap if there is a double tap handler
- if (!HasDoubleTapHandler())
- return false;
-
- return _tapDelegate(1);
- }
-
bool GestureDetector.IOnGestureListener.OnDown(MotionEvent e)
{
SetStartingPosition(e);
+
+ if (HasAnyGestures())
+ {
+ // If we have any gestures to listen for, we need to return true to show we're interested in the rest
+ // of the events.
+ return true;
+ }
+
+ // Since we don't have any gestures we're listening for, we return false to show we're not interested
+ // and let parent controls have a whack at the events
return false;
}
@@ -119,10 +115,32 @@ namespace Xamarin.Forms.Platform.Android
if (_disposed)
return false;
- // optimization: do not wait for a second tap if there is no double tap handler
if (HasDoubleTapHandler())
+ {
+ // Because we have a handler for double-tap, we need to wait for
+ // OnSingleTapConfirmed (to verify it's really just a single tap) before running the delegate
return false;
+ }
+
+ // A single tap has occurred and there's no handler for double tap to worry about,
+ // so we can go ahead and run the delegate
+ return _tapDelegate(1);
+ }
+
+ bool GestureDetector.IOnDoubleTapListener.OnSingleTapConfirmed(MotionEvent e)
+ {
+ if (_disposed)
+ return false;
+
+ if (!HasDoubleTapHandler())
+ {
+ // We're not worried about double-tap, so OnSingleTap has already run the delegate
+ // there's nothing for us to do here
+ return false;
+ }
+ // Since there was a double-tap handler, we had to wait for OnSingleTapConfirmed;
+ // Now that we're sure it's a single tap, we can run the delegate
return _tapDelegate(1);
}
diff --git a/Xamarin.Forms.Platform.Android/InnerScaleListener.cs b/Xamarin.Forms.Platform.Android/InnerScaleListener.cs
index 4a6c6581..76f7629a 100644
--- a/Xamarin.Forms.Platform.Android/InnerScaleListener.cs
+++ b/Xamarin.Forms.Platform.Android/InnerScaleListener.cs
@@ -10,20 +10,16 @@ namespace Xamarin.Forms.Platform.Android
Action _pinchEndedDelegate;
Func<Point, bool> _pinchStartedDelegate;
- public InnerScaleListener(Func<float, Point, bool> pinchDelegate, Func<Point, bool> pinchStarted, Action pinchEnded)
+ public InnerScaleListener(PinchGestureHandler pinchGestureHandler)
{
- if (pinchDelegate == null)
- throw new ArgumentNullException("pinchDelegate");
-
- if (pinchStarted == null)
- throw new ArgumentNullException("pinchStarted");
-
- if (pinchEnded == null)
- throw new ArgumentNullException("pinchEnded");
+ if (pinchGestureHandler == null)
+ {
+ throw new ArgumentNullException(nameof(pinchGestureHandler));
+ }
- _pinchDelegate = pinchDelegate;
- _pinchStartedDelegate = pinchStarted;
- _pinchEndedDelegate = pinchEnded;
+ _pinchDelegate = pinchGestureHandler.OnPinch;
+ _pinchStartedDelegate = pinchGestureHandler.OnPinchStarted;
+ _pinchEndedDelegate = pinchGestureHandler.OnPinchEnded;
}
// This is needed because GestureRecognizer callbacks can be delayed several hundred milliseconds
diff --git a/Xamarin.Forms.Platform.Android/PanGestureHandler.cs b/Xamarin.Forms.Platform.Android/PanGestureHandler.cs
index 0837f633..32554d52 100644
--- a/Xamarin.Forms.Platform.Android/PanGestureHandler.cs
+++ b/Xamarin.Forms.Platform.Android/PanGestureHandler.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android
@@ -66,5 +67,11 @@ namespace Xamarin.Forms.Platform.Android
}
return result;
}
+
+ public bool HasAnyGestures()
+ {
+ var view = GetView();
+ return view != null && view.GestureRecognizers.OfType<PanGestureRecognizer>().Any();
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/PinchGestureHandler.cs b/Xamarin.Forms.Platform.Android/PinchGestureHandler.cs
index c315b92c..e5d31373 100644
--- a/Xamarin.Forms.Platform.Android/PinchGestureHandler.cs
+++ b/Xamarin.Forms.Platform.Android/PinchGestureHandler.cs
@@ -13,19 +13,11 @@ namespace Xamarin.Forms.Platform.Android
GetView = getView;
}
- public bool IsPinchSupported
- {
- get
- {
- View view = GetView();
- return view != null && view.GestureRecognizers.GetGesturesFor<PinchGestureRecognizer>().Any();
- }
- }
-
Func<View> GetView { get; }
// A View can have at most one pinch gesture, so we just need to look for one (or none)
- PinchGestureRecognizer PinchGesture => GetView()?.GestureRecognizers.GetGesturesFor<PinchGestureRecognizer>().FirstOrDefault();
+ PinchGestureRecognizer PinchGesture => GetView()?.GestureRecognizers.OfType<PinchGestureRecognizer>()
+ .FirstOrDefault();
public bool OnPinch(float scale, Point scalePoint)
{
@@ -39,7 +31,7 @@ namespace Xamarin.Forms.Platform.Android
return true;
var scalePointTransformed = new Point(scalePoint.X / view.Width, scalePoint.Y / view.Height);
- ((IPinchGestureController)pinchGesture).SendPinch(view, 1 + (scale - 1) * _pinchStartingScale, scalePointTransformed);
+ pinchGesture.SendPinch(view, 1 + (scale - 1) * _pinchStartingScale, scalePointTransformed);
return true;
}
@@ -52,7 +44,7 @@ namespace Xamarin.Forms.Platform.Android
return;
PinchGestureRecognizer pinchGesture = PinchGesture;
- ((IPinchGestureController)pinchGesture)?.SendPinchEnded(view);
+ pinchGesture?.SendPinchEnded(view);
}
public bool OnPinchStarted(Point scalePoint)
@@ -70,7 +62,7 @@ namespace Xamarin.Forms.Platform.Android
var scalePointTransformed = new Point(scalePoint.X / view.Width, scalePoint.Y / view.Height);
- ((IPinchGestureController)pinchGesture).SendPinchStarted(view, scalePointTransformed);
+ pinchGesture.SendPinchStarted(view, scalePointTransformed);
return true;
}
}
diff --git a/Xamarin.Forms.Platform.Android/Platform.cs b/Xamarin.Forms.Platform.Android/Platform.cs
index c0fc2099..ebdf5e96 100644
--- a/Xamarin.Forms.Platform.Android/Platform.cs
+++ b/Xamarin.Forms.Platform.Android/Platform.cs
@@ -1055,11 +1055,9 @@ namespace Xamarin.Forms.Platform.Android
internal class DefaultRenderer : VisualElementRenderer<View>
{
bool _notReallyHandled;
- Dictionary<int, float> _minimumElevation = new Dictionary<int, float>();
public DefaultRenderer()
{
- ChildrenDrawingOrderEnabled = true;
}
readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
@@ -1069,11 +1067,6 @@ namespace Xamarin.Forms.Platform.Android
_notReallyHandled = true;
}
- internal void InvalidateMinimumElevation()
- {
- _minimumElevation = new Dictionary<int, float>();
- }
-
public override bool OnTouchEvent(MotionEvent e)
{
if (base.OnTouchEvent(e))
@@ -1128,31 +1121,8 @@ namespace Xamarin.Forms.Platform.Android
}
return result;
- }
-
- protected override int GetChildDrawingOrder(int childCount, int i)
- {
- //On Material design the button states use Elevation property, we need to make sure
- //we update the elevation of other controls to be over the previous one
- if (Forms.IsLollipopOrNewer)
- {
- if (!_minimumElevation.ContainsKey(i))
- {
- _minimumElevation[i] = GetChildAt(i).Elevation;
- }
-
- for (int j = 0; j < _minimumElevation.Count() - 1; j++)
{
- while (_minimumElevation[j] > _minimumElevation[j + 1])
- {
- _minimumElevation[j + 1] = _minimumElevation[j] + 1;
- GetChildAt(j + 1).Elevation = _minimumElevation[j + 1];
- }
- if (j == i)
- break;
}
- }
- return base.GetChildDrawingOrder(childCount, i);
}
}
diff --git a/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
index 077cf159..1172f59b 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
@@ -106,12 +106,12 @@ namespace Xamarin.Forms.Platform.Android
await Control.UpdateBitmap(Element, previous);
}
- public override bool OnTouchEvent(MotionEvent e)
- {
- if (base.OnTouchEvent(e))
- return true;
-
- return _motionEventHelper.HandleMotionEvent(Parent, e);
- }
- }
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ if (base.OnTouchEvent(e))
+ return true;
+
+ return _motionEventHelper.HandleMotionEvent(Parent, e);
+ }
+ }
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs
index b911656d..d9f06c54 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs
@@ -150,9 +150,24 @@ namespace Xamarin.Forms.Platform.Android
UpdateFooter();
UpdateIsSwipeToRefreshEnabled();
UpdateFastScrollEnabled();
+
+
}
}
+ internal void LongClickOn(AView viewCell)
+ {
+ if (Control == null)
+ {
+ return;
+ }
+
+ var position = Control.GetPositionForView(viewCell);
+ var id = Control.GetItemIdAtPosition(position);
+
+ _adapter.OnItemLongClick(Control, viewCell, position, id);
+ }
+
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
diff --git a/Xamarin.Forms.Platform.Android/Renderers/ViewCellExtensions.cs b/Xamarin.Forms.Platform.Android/Renderers/ViewCellExtensions.cs
index 278e284a..c7504ab4 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/ViewCellExtensions.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/ViewCellExtensions.cs
@@ -19,24 +19,5 @@ namespace Xamarin.Forms.Platform.Android
return false;
}
-
- public static void EnsureLongClickCancellation(this AView view, MotionEvent motionEvent, bool handled, VisualElement element)
- {
- if (view.IsDisposed())
- {
- return;
- }
-
- if (motionEvent.Action == MotionEventActions.Up && handled && view.LongClickable && element.IsInViewCell())
- {
- // In order for long presses/clicks (for opening context menus) to work in a ViewCell
- // which contains any Clickable Views (e.g., any with TapGestures associated, or Buttons)
- // the top-level container in the ViewCell has to be LongClickable; unfortunately, Android
- // cancels a pending long press/click during MotionEventActions.Up, which the View won't
- // get if the gesture listener has already processed it. So when all these conditions are
- // true, we need to go ahead and send the Up event to the View; if we don't, the context menu will open
- view.OnTouchEvent(motionEvent);
- }
- }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/TapGestureHandler.cs b/Xamarin.Forms.Platform.Android/TapGestureHandler.cs
index 0dd63262..56d06417 100644
--- a/Xamarin.Forms.Platform.Android/TapGestureHandler.cs
+++ b/Xamarin.Forms.Platform.Android/TapGestureHandler.cs
@@ -41,6 +41,12 @@ namespace Xamarin.Forms.Platform.Android
return result;
}
+ public bool HasAnyGestures()
+ {
+ var view = GetView();
+ return view != null && view.GestureRecognizers.OfType<TapGestureRecognizer>().Any();
+ }
+
public IEnumerable<TapGestureRecognizer> TapGestureRecognizers(int count)
{
View view = GetView();
diff --git a/Xamarin.Forms.Platform.Android/VisualElementExtensions.cs b/Xamarin.Forms.Platform.Android/VisualElementExtensions.cs
index 268b3af2..a39cc88e 100644
--- a/Xamarin.Forms.Platform.Android/VisualElementExtensions.cs
+++ b/Xamarin.Forms.Platform.Android/VisualElementExtensions.cs
@@ -13,19 +13,5 @@ namespace Xamarin.Forms.Platform.Android
return renderer;
}
-
- public static bool ShouldBeMadeClickable(this View view)
- {
- for (var i = 0; i < view.GestureRecognizers.Count; i++)
- {
- IGestureRecognizer gesture = view.GestureRecognizers[i];
- if (gesture is TapGestureRecognizer || gesture is PinchGestureRecognizer || gesture is PanGestureRecognizer)
- {
- return true;
- }
- }
-
- return false;
- }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/VisualElementPackager.cs b/Xamarin.Forms.Platform.Android/VisualElementPackager.cs
index 91781182..1d886629 100644
--- a/Xamarin.Forms.Platform.Android/VisualElementPackager.cs
+++ b/Xamarin.Forms.Platform.Android/VisualElementPackager.cs
@@ -120,7 +120,6 @@ namespace Xamarin.Forms.Platform.Android
(_renderer.View as ViewGroup)?.BringChildToFront(r.View);
}
}
- (_renderer as Platform.DefaultRenderer)?.InvalidateMinimumElevation();
}
void OnChildAdded(object sender, ElementEventArgs e)
diff --git a/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs b/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs
index 120cc972..098e85f6 100644
--- a/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs
@@ -1,53 +1,66 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Collections.Specialized;
using System.ComponentModel;
using Android.Support.V4.View;
using Android.Views;
using Xamarin.Forms.Internals;
+using Xamarin.Forms.Platform.Android.FastRenderers;
using AView = Android.Views.View;
namespace Xamarin.Forms.Platform.Android
{
- public abstract class VisualElementRenderer<TElement> : FormsViewGroup, IVisualElementRenderer, AView.IOnTouchListener, AView.IOnClickListener, IEffectControlProvider where TElement : VisualElement
+ public abstract class VisualElementRenderer<TElement> : FormsViewGroup, IVisualElementRenderer,
+ IEffectControlProvider where TElement : VisualElement
{
readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = new List<EventHandler<VisualElementChangedEventArgs>>();
- readonly Lazy<GestureDetector> _gestureDetector;
- readonly PanGestureHandler _panGestureHandler;
- readonly PinchGestureHandler _pinchGestureHandler;
- readonly TapGestureHandler _tapGestureHandler;
-
- NotifyCollectionChangedEventHandler _collectionChangeHandler;
-
VisualElementRendererFlags _flags = VisualElementRendererFlags.AutoPackage | VisualElementRendererFlags.AutoTrack;
string _defaultContentDescription;
bool? _defaultFocusable;
string _defaultHint;
int? _defaultLabelFor;
- InnerGestureListener _gestureListener;
+
VisualElementPackager _packager;
PropertyChangedEventHandler _propertyChangeHandler;
- Lazy<ScaleGestureDetector> _scaleDetector;
+
+ readonly GestureManager _gestureManager;
protected VisualElementRenderer() : base(Forms.Context)
{
- _tapGestureHandler = new TapGestureHandler(() => View);
- _panGestureHandler = new PanGestureHandler(() => View, Context.FromPixels);
- _pinchGestureHandler = new PinchGestureHandler(() => View);
-
- _gestureDetector =
- new Lazy<GestureDetector>(
- () =>
- new GestureDetector(
- _gestureListener =
- new InnerGestureListener(_tapGestureHandler.OnTap, _tapGestureHandler.TapGestureRecognizers, _panGestureHandler.OnPan, _panGestureHandler.OnPanStarted, _panGestureHandler.OnPanComplete)));
-
- _scaleDetector = new Lazy<ScaleGestureDetector>(
- () => new ScaleGestureDetector(Context, new InnerScaleListener(_pinchGestureHandler.OnPinch, _pinchGestureHandler.OnPinchStarted, _pinchGestureHandler.OnPinchEnded))
- );
+ _gestureManager = new GestureManager(this);
+ }
+
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ return _gestureManager.OnTouchEvent(e);
+ }
+
+ public override bool OnInterceptTouchEvent(MotionEvent ev)
+ {
+ if (!Enabled)
+ {
+ // If Enabled is false, prevent all the events from being dispatched to child Views
+ // and prevent them from being processed by this View as well
+ return true; // IOW, intercepted
+ }
+
+ return base.OnInterceptTouchEvent(ev);
+ }
+
+ public override bool DispatchTouchEvent(MotionEvent e)
+ {
+ if (InputTransparent)
+ {
+ // If the Element is InputTransparent, this ViewGroup will be marked InputTransparent
+ // If we're InputTransparent we should return false on all touch events without
+ // even bothering to send them to the child Views
+
+ return false; // IOW, not handled
+ }
+
+ return base.DispatchTouchEvent(e);
}
public TElement Element { get; private set; }
@@ -85,54 +98,6 @@ namespace Xamarin.Forms.Platform.Android
OnRegisterEffect(platformEffect);
}
- void AView.IOnClickListener.OnClick(AView v)
- {
- _tapGestureHandler.OnSingleClick();
- }
-
- public override bool OnInterceptTouchEvent(MotionEvent ev)
- {
- if (!Element.IsEnabled || (Element.InputTransparent && Element.IsEnabled))
- {
- return true;
- }
-
- return base.OnInterceptTouchEvent(ev);
- }
-
- bool AView.IOnTouchListener.OnTouch(AView v, MotionEvent e)
- {
- if (!Element.IsEnabled)
- return true;
-
- if (Element.InputTransparent)
- return false;
-
- var handled = false;
- if (_pinchGestureHandler.IsPinchSupported)
- {
- if (!_scaleDetector.IsValueCreated)
- ScaleGestureDetectorCompat.SetQuickScaleEnabled(_scaleDetector.Value, true);
- handled = _scaleDetector.Value.OnTouchEvent(e);
- }
-
- _gestureListener?.OnTouchEvent(e);
-
- if (_gestureDetector.IsValueCreated && _gestureDetector.Value.Handle == IntPtr.Zero)
- {
- // This gesture detector has already been disposed, probably because it's on a cell which is going away
- return handled;
- }
-
- // It's very important that the gesture detection happen first here
- // if we check handled first, we might short-circuit and never check for tap/pan
- handled = _gestureDetector.Value.OnTouchEvent(e) || handled;
-
- v.EnsureLongClickCancellation(e, handled, Element);
-
- return handled;
- }
-
VisualElement IVisualElementRenderer.Element => Element;
event EventHandler<VisualElementChangedEventArgs> IVisualElementRenderer.ElementChanged
@@ -183,7 +148,6 @@ namespace Xamarin.Forms.Platform.Android
if (oldElement != null)
{
oldElement.PropertyChanged -= _propertyChangeHandler;
- UnsubscribeGestureRecognizers(oldElement);
}
// element may be allowed to be passed as null in the future
@@ -198,19 +162,12 @@ namespace Xamarin.Forms.Platform.Android
_propertyChangeHandler = OnElementPropertyChanged;
element.PropertyChanged += _propertyChangeHandler;
- SubscribeGestureRecognizers(element);
if (oldElement == null)
{
- SetOnClickListener(this);
- SetOnTouchListener(this);
SoundEffectsEnabled = false;
}
- // must be updated AFTER SetOnClickListener is called
- // SetOnClickListener implicitly calls Clickable = true
- UpdateGestureRecognizers(true);
-
OnElementChanged(new ElementChangedEventArgs<TElement>(oldElement, element));
if (AutoPackage && _packager == null)
@@ -263,18 +220,6 @@ namespace Xamarin.Forms.Platform.Android
_packager = null;
}
- if (_scaleDetector != null && _scaleDetector.IsValueCreated)
- {
- _scaleDetector.Value.Dispose();
- _scaleDetector = null;
- }
-
- if (_gestureListener != null)
- {
- _gestureListener.Dispose();
- _gestureListener = null;
- }
-
if (ManageNativeControlLifetime)
{
int count = ChildCount;
@@ -290,7 +235,6 @@ namespace Xamarin.Forms.Platform.Android
if (Element != null)
{
Element.PropertyChanged -= _propertyChangeHandler;
- UnsubscribeGestureRecognizers(Element);
if (Platform.GetRenderer(Element) == this)
Platform.SetRenderer(Element, null);
@@ -328,6 +272,7 @@ namespace Xamarin.Forms.Platform.Android
SetFocusable();
else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
UpdateInputTransparent();
+
ElementPropertyChanged?.Invoke(this, e);
}
@@ -441,11 +386,6 @@ namespace Xamarin.Forms.Platform.Android
element.SendViewInitialized(nativeView);
}
- void HandleGestureRecognizerCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
- {
- UpdateGestureRecognizers();
- }
-
void IVisualElementRenderer.SetLabelFor(int? id)
{
if (_defaultLabelFor == null)
@@ -453,47 +393,5 @@ namespace Xamarin.Forms.Platform.Android
LabelFor = (int)(id ?? _defaultLabelFor);
}
-
- void SubscribeGestureRecognizers(VisualElement element)
- {
- var view = element as View;
- if (view == null)
- return;
-
- if (_collectionChangeHandler == null)
- _collectionChangeHandler = HandleGestureRecognizerCollectionChanged;
-
- var observableCollection = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers;
- observableCollection.CollectionChanged += _collectionChangeHandler;
- }
-
- void UnsubscribeGestureRecognizers(VisualElement element)
- {
- var view = element as View;
- if (view == null || _collectionChangeHandler == null)
- return;
-
- var observableCollection = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers;
- observableCollection.CollectionChanged -= _collectionChangeHandler;
- }
-
- void UpdateClickable(bool force = false)
- {
- var view = Element as View;
- if (view == null)
- return;
-
- bool newValue = view.ShouldBeMadeClickable();
- if (force || newValue)
- Clickable = newValue;
- }
-
- void UpdateGestureRecognizers(bool forceClick = false)
- {
- if (View == null)
- return;
-
- UpdateClickable(forceClick);
- }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
index 91f5db78..8539ddc0 100644
--- a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
+++ b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
@@ -125,7 +125,7 @@
<Compile Include="ExportCellAttribute.cs" />
<Compile Include="ExportImageSourceHandlerAttribute.cs" />
<Compile Include="ExportRendererAttribute.cs" />
- <Compile Include="FastRenderers\GestureManager.cs" />
+ <Compile Include="GestureManager.cs" />
<Compile Include="FastRenderers\LabelRenderer.cs" />
<Compile Include="FastRenderers\VisualElementRenderer.cs" />
<Compile Include="Flags.cs" />