From e9f655688586bc0eb4183507def7ed3f774b7c71 Mon Sep 17 00:00:00 2001 From: Jason Smith Date: Fri, 8 Apr 2016 09:16:02 -0700 Subject: [PATCH] [A] Fix Bugzilla 40173 (#62) * [A]Fix issue where Frame would block all tap gestures under it even if it didn't have one * [A]Make BoxView non-blocking inside a ListView only * Add UITest for bz40173 * Fix code review issues --- .../Bugzilla40173.cs | 110 +++++++++++++++++++++ .../Xamarin.Forms.Controls.Issues.Shared.projitems | 1 + .../AppCompat/FrameRenderer.cs | 63 +++++++++++- .../Renderers/BoxRenderer.cs | 22 ++++- 4 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla40173.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla40173.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla40173.cs new file mode 100644 index 0000000..422e363 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla40173.cs @@ -0,0 +1,110 @@ +using Xamarin.Forms.CustomAttributes; +#if UITEST +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Bugzilla, 40173, "Android BoxView/Frame not clickthrough in ListView")] + public class Bugzilla40173 : TestContentPage // or TestMasterDetailPage, etc ... + { + const string CantTouchButtonId = "CantTouchButtonId"; + const string CanTouchButtonId = "CanTouchButtonId"; + const string ListTapTarget = "ListTapTarget"; + const string CantTouchFailText = "Failed"; + const string CanTouchSuccessText = "ButtonTapped"; + const string ListTapSuccessText = "ItemTapped"; + +#if UITEST + [Test] + public void ButtonBlocked() + { + RunningApp.Tap(q => q.Marked(CantTouchButtonId)); + RunningApp.WaitForNoElement(q => q.Text(CantTouchFailText)); + + RunningApp.Tap(q => q.Marked(CanTouchButtonId)); + RunningApp.WaitForElement(q => q.Text(CanTouchSuccessText)); + + RunningApp.Tap(q => q.Marked(ListTapTarget)); + RunningApp.WaitForElement(q => q.Text(ListTapSuccessText)); + } +#endif + + protected override void Init() + { + var outputLabel = new Label(); + var testButton = new Button + { + Text = "Can't Touch This", + AutomationId = CantTouchButtonId + }; + + testButton.Clicked += (sender, args) => outputLabel.Text = CantTouchFailText; + + var testGrid = new Grid + { + Children = + { + testButton, + new BoxView + { + Color = Color.Pink.MultiplyAlpha(0.5) + } + } + }; + + // BoxView over Button prevents Button click + var testButtonOk = new Button + { + Text = "Can Touch This", + AutomationId = CanTouchButtonId + }; + + testButtonOk.Clicked += (sender, args) => outputLabel.Text = CanTouchSuccessText; + + var testGridOk = new Grid + { + Children = + { + testButtonOk, + new BoxView + { + Color = Color.Pink.MultiplyAlpha(0.5), + InputTransparent = true + } + } + }; + + var testListView = new ListView(); + var items = new[] { "Foo" }; + testListView.ItemsSource = items; + testListView.ItemTemplate = new DataTemplate(() => + { + var result = new ViewCell + { + View = new Grid + { + Children = + { + new BoxView + { + AutomationId = ListTapTarget, + Color = Color.Pink.MultiplyAlpha(0.5) + } + } + } + }; + + return result; + }); + + testListView.ItemSelected += (sender, args) => outputLabel.Text = ListTapSuccessText; + + Content = new StackLayout + { + Children = { outputLabel, testGrid, testGridOk, testListView } + }; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index e13847b..e7afb08 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 @@ -95,6 +95,7 @@ Code + diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs index 7b544b2..232b6a6 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.ComponentModel; using Android.Content; using Android.Support.V4.View; @@ -19,11 +21,13 @@ namespace Xamarin.Forms.Platform.Android.AppCompat float _defaultElevation = -1f; + bool _clickable; bool _disposed; Frame _element; InnerGestureListener _gestureListener; VisualElementPackager _visualElementPackager; VisualElementTracker _visualElementTracker; + NotifyCollectionChangedEventHandler _collectionChangeHandler; public FrameRenderer() : base(Forms.Context) { @@ -147,17 +151,20 @@ namespace Xamarin.Forms.Platform.Android.AppCompat ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement)); if (e.OldElement != null) - e.OldElement.PropertyChanged -= OnElementPropertyChanged; - else { - SetOnClickListener(this); - SetOnTouchListener(this); + e.OldElement.PropertyChanged -= OnElementPropertyChanged; + UnsubscribeGestureRecognizers(e.OldElement); } if (e.NewElement != null) { if (_visualElementTracker == null) { + SetOnClickListener(this); + SetOnTouchListener(this); + + UpdateGestureRecognizers(true); + _visualElementTracker = new VisualElementTracker(this); _visualElementPackager = new VisualElementPackager(this); _visualElementPackager.Load(); @@ -166,6 +173,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat e.NewElement.PropertyChanged += OnElementPropertyChanged; UpdateShadow(); UpdateBackgroundColor(); + SubscribeGestureRecognizers(e.NewElement); } } @@ -185,6 +193,11 @@ namespace Xamarin.Forms.Platform.Android.AppCompat } } + void HandleGestureRecognizerCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) + { + UpdateGestureRecognizers(); + } + void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == Frame.HasShadowProperty.PropertyName) @@ -193,12 +206,54 @@ namespace Xamarin.Forms.Platform.Android.AppCompat UpdateBackgroundColor(); } + void SubscribeGestureRecognizers(VisualElement element) + { + var view = element as View; + if (view == null) + return; + + if (_collectionChangeHandler == null) + _collectionChangeHandler = HandleGestureRecognizerCollectionChanged; + + var observableCollection = (ObservableCollection)view.GestureRecognizers; + observableCollection.CollectionChanged += _collectionChangeHandler; + } + + void UnsubscribeGestureRecognizers(VisualElement element) + { + var view = element as View; + if (view == null || _collectionChangeHandler == null) + return; + + var observableCollection = (ObservableCollection)view.GestureRecognizers; + observableCollection.CollectionChanged -= _collectionChangeHandler; + } + void UpdateBackgroundColor() { Color bgColor = Element.BackgroundColor; SetCardBackgroundColor(bgColor.IsDefault ? AColor.White : bgColor.ToAndroid()); } + void UpdateClickable(bool force = false) + { + var view = Element as View; + if (view == null) + return; + + bool newValue = view.ShouldBeMadeClickable(); + if (force || _clickable != newValue) + Clickable = newValue; + } + + void UpdateGestureRecognizers(bool forceClick = false) + { + if (Element == null) + return; + + UpdateClickable(forceClick); + } + void UpdateShadow() { float elevation = _defaultElevation; diff --git a/Xamarin.Forms.Platform.Android/Renderers/BoxRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/BoxRenderer.cs index 725829f..b29dd76 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/BoxRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/BoxRenderer.cs @@ -5,6 +5,8 @@ namespace Xamarin.Forms.Platform.Android { public class BoxRenderer : VisualElementRenderer { + bool _isInViewCell; + public BoxRenderer() { AutoPackage = false; @@ -12,13 +14,29 @@ namespace Xamarin.Forms.Platform.Android public override bool OnTouchEvent(MotionEvent e) { - base.OnTouchEvent(e); - return !Element.InputTransparent; + if (base.OnTouchEvent(e)) + return true; + return !Element.InputTransparent && !_isInViewCell; } protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); + + if (e.NewElement != null) + { + var parent = e.NewElement.Parent; + while (parent != null) + { + if (parent is ViewCell) + { + _isInViewCell = true; + break; + } + parent = parent.Parent; + } + } + UpdateBackgroundColor(); } -- 2.7.4