diff options
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" /> |