* [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
--- /dev/null
+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
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla39702.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla40173.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CarouselAsync.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34561.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34727.cs" />
using System;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
using System.ComponentModel;
using Android.Content;
using Android.Support.V4.View;
float _defaultElevation = -1f;
+ bool _clickable;
bool _disposed;
Frame _element;
InnerGestureListener _gestureListener;
VisualElementPackager _visualElementPackager;
VisualElementTracker _visualElementTracker;
+ NotifyCollectionChangedEventHandler _collectionChangeHandler;
public FrameRenderer() : base(Forms.Context)
{
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();
e.NewElement.PropertyChanged += OnElementPropertyChanged;
UpdateShadow();
UpdateBackgroundColor();
+ SubscribeGestureRecognizers(e.NewElement);
}
}
}
}
+ void HandleGestureRecognizerCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+ {
+ UpdateGestureRecognizers();
+ }
+
void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == Frame.HasShadowProperty.PropertyName)
UpdateBackgroundColor();
}
+ 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 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;
{
public class BoxRenderer : VisualElementRenderer<BoxView>
{
+ bool _isInViewCell;
+
public BoxRenderer()
{
AutoPackage = false;
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<BoxView> 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();
}