summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.Android
diff options
context:
space:
mode:
authorE.Z. Hart <hartez@users.noreply.github.com>2017-03-23 11:18:38 -0600
committerRui Marinho <me@ruimarinho.net>2017-03-23 17:18:38 +0000
commitf27f5a3650f37894d4a1ac925d6fab4dc7350087 (patch)
treeb368c6c35f6592ef28e638c431bb5f3012f58a93 /Xamarin.Forms.Platform.Android
parent2be80a55a514a050ab5ab07a201d13c111f49f63 (diff)
downloadxamarin-forms-f27f5a3650f37894d4a1ac925d6fab4dc7350087.tar.gz
xamarin-forms-f27f5a3650f37894d4a1ac925d6fab4dc7350087.tar.bz2
xamarin-forms-f27f5a3650f37894d4a1ac925d6fab4dc7350087.zip
UI tests for InputTransparent and fixes for Android/Windows (#808)
* Set up automated UI tests for InputTransparent * Pull in Adrian's UI tests from PR 483 * Fix bugs with box/label/image gestures passing through when not transparent * Fix disabling of layouts on Windows; fix 44096 test for iOS/Windows; * Automate the 53445 test
Diffstat (limited to 'Xamarin.Forms.Platform.Android')
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs26
-rw-r--r--Xamarin.Forms.Platform.Android/Platform.cs44
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/BoxRenderer.cs19
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs14
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/LabelRenderer.cs14
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/MotionEventHelper.cs53
-rw-r--r--Xamarin.Forms.Platform.Android/VisualElementRenderer.cs28
-rw-r--r--Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj1
8 files changed, 172 insertions, 27 deletions
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs
index 7c40ea80..d0ed345d 100644
--- a/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs
@@ -31,6 +31,8 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
VisualElementTracker _visualElementTracker;
NotifyCollectionChangedEventHandler _collectionChangeHandler;
+ bool _inputTransparent;
+
public FrameRenderer() : base(Forms.Context)
{
_tapGestureHandler = new TapGestureHandler(() => Element);
@@ -69,6 +71,16 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
}
}
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ if (_inputTransparent)
+ {
+ return false;
+ }
+
+ return base.OnTouchEvent(e);
+ }
+
void IOnClickListener.OnClick(AView v)
{
_tapGestureHandler.OnSingleClick();
@@ -83,14 +95,16 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
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;
}
- return _gestureDetector.Value.OnTouchEvent(e) || 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
+ return _gestureDetector.Value.OnTouchEvent(e) || handled;
}
VisualElement IVisualElementRenderer.Element => Element;
@@ -202,6 +216,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
UpdateShadow();
UpdateBackgroundColor();
UpdateCornerRadius();
+ UpdateInputTransparent();
SubscribeGestureRecognizers(e.NewElement);
}
}
@@ -235,6 +250,13 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
UpdateBackgroundColor();
else if (e.PropertyName == Frame.CornerRadiusProperty.PropertyName)
UpdateCornerRadius();
+ else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
+ UpdateInputTransparent();
+ }
+
+ void UpdateInputTransparent()
+ {
+ _inputTransparent = Element.InputTransparent;
}
void SubscribeGestureRecognizers(VisualElement element)
diff --git a/Xamarin.Forms.Platform.Android/Platform.cs b/Xamarin.Forms.Platform.Android/Platform.cs
index ca0b9708..0fc287c9 100644
--- a/Xamarin.Forms.Platform.Android/Platform.cs
+++ b/Xamarin.Forms.Platform.Android/Platform.cs
@@ -1024,6 +1024,50 @@ namespace Xamarin.Forms.Platform.Android
internal class DefaultRenderer : VisualElementRenderer<View>
{
+ bool _notReallyHandled;
+ internal void NotifyFakeHandling()
+ {
+ _notReallyHandled = true;
+ }
+
+ public override bool DispatchTouchEvent(MotionEvent e)
+ {
+ #region
+ // Normally dispatchTouchEvent feeds the touch events to its children one at a time, top child first,
+ // (and only to the children in the hit-test area of the event) stopping as soon as one of them has handled
+ // the event.
+
+ // But to be consistent across the platforms, we don't want this behavior; if an element is not input transparent
+ // we don't want an event to "pass through it" and be handled by an element "behind/under" it. We just want the processing
+ // to end after the first non-transparent child, regardless of whether the event has been handled.
+
+ // This is only an issue for a couple of controls; the interactive controls (switch, button, slider, etc) already "handle" their touches
+ // and the events don't propagate to other child controls. But for image, label, and box that doesn't happen. We can't have those controls
+ // lie about their events being handled because then the events won't propagate to *parent* controls (e.g., a frame with a label in it would
+ // never get a tap gesture from the label). In other words, we *want* parent propagation, but *do not want* sibling propagation. So we need to short-circuit
+ // base.DispatchTouchEvent here, but still return "false".
+
+ // Duplicating the logic of ViewGroup.dispatchTouchEvent and modifying it slightly for our purposes is a non-starter; the method is too
+ // complex and does a lot of micro-optimization. Instead, we provide a signalling mechanism for the controls which don't already "handle" touch
+ // events to tell us that they will be lying about handling their event; they then return "true" to short-circuit base.DispatchTouchEvent.
+
+ // The container gets this message and after it gets the "handled" result from dispatchTouchEvent,
+ // it then knows to ignore that result and return false/unhandled. This allows the event to propagate up the tree.
+ #endregion
+
+ _notReallyHandled = false;
+
+ var result = base.DispatchTouchEvent(e);
+
+ if (result && _notReallyHandled)
+ {
+ // If the child control returned true from its touch event handler but signalled that it was a fake "true", leave the event unhandled
+ // so parent controls have the opportunity
+ return false;
+ }
+
+ return result;
+ }
}
#region IPlatformEngine implementation
diff --git a/Xamarin.Forms.Platform.Android/Renderers/BoxRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/BoxRenderer.cs
index 380d4018..aa19d81c 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/BoxRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/BoxRenderer.cs
@@ -5,7 +5,7 @@ namespace Xamarin.Forms.Platform.Android
{
public class BoxRenderer : VisualElementRenderer<BoxView>
{
- bool _isInViewCell;
+ readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
public BoxRenderer()
{
@@ -16,26 +16,15 @@ namespace Xamarin.Forms.Platform.Android
{
if (base.OnTouchEvent(e))
return true;
- return !Element.InputTransparent && !_isInViewCell;
+
+ return _motionEventHelper.HandleMotionEvent(Parent);
}
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;
- }
- }
+ _motionEventHelper.UpdateElement(e.NewElement);
UpdateBackgroundColor();
}
diff --git a/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
index e18d4b95..fee05f1b 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
@@ -3,6 +3,7 @@ using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using Android.Graphics;
+using Android.Views;
using AImageView = Android.Widget.ImageView;
using Xamarin.Forms.Internals;
@@ -11,8 +12,7 @@ namespace Xamarin.Forms.Platform.Android
public class ImageRenderer : ViewRenderer<Image, AImageView>
{
bool _isDisposed;
-
- IElementController ElementController => Element as IElementController;
+ readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
public ImageRenderer()
{
@@ -44,6 +44,8 @@ namespace Xamarin.Forms.Platform.Android
SetNativeControl(view);
}
+ _motionEventHelper.UpdateElement(e.NewElement);
+
UpdateBitmap(e.OldElement);
UpdateAspect();
}
@@ -117,5 +119,13 @@ namespace Xamarin.Forms.Platform.Android
((IVisualElementController)Element).NativeSizeChanged();
}
}
+
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ if (base.OnTouchEvent(e))
+ return true;
+
+ return _motionEventHelper.HandleMotionEvent(Parent);
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Renderers/LabelRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/LabelRenderer.cs
index c544a239..ab1a6a35 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/LabelRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/LabelRenderer.cs
@@ -4,8 +4,8 @@ using Android.Content.Res;
using Android.Graphics;
using Android.Text;
using Android.Util;
+using Android.Views;
using Android.Widget;
-using AColor = Android.Graphics.Color;
namespace Xamarin.Forms.Platform.Android
{
@@ -23,6 +23,8 @@ namespace Xamarin.Forms.Platform.Android
FormsTextView _view;
bool _wasFormatted;
+ readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
+
public LabelRenderer()
{
AutoPackage = false;
@@ -97,6 +99,8 @@ namespace Xamarin.Forms.Platform.Android
if (e.OldElement.HorizontalTextAlignment != e.NewElement.HorizontalTextAlignment || e.OldElement.VerticalTextAlignment != e.NewElement.VerticalTextAlignment)
UpdateGravity();
}
+
+ _motionEventHelper.UpdateElement(e.NewElement);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
@@ -217,5 +221,13 @@ namespace Xamarin.Forms.Platform.Android
_lastSizeRequest = null;
}
+
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ if (base.OnTouchEvent(e))
+ return true;
+
+ return _motionEventHelper.HandleMotionEvent(Parent);
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Renderers/MotionEventHelper.cs b/Xamarin.Forms.Platform.Android/Renderers/MotionEventHelper.cs
new file mode 100644
index 00000000..4ed06d26
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/Renderers/MotionEventHelper.cs
@@ -0,0 +1,53 @@
+using Android.Views;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ internal class MotionEventHelper
+ {
+ VisualElement _element;
+ bool _isInViewCell;
+
+ public bool HandleMotionEvent(IViewParent parent)
+ {
+ if (_isInViewCell || _element.InputTransparent)
+ {
+ return false;
+ }
+
+ var renderer = parent as Platform.DefaultRenderer;
+ if (renderer == null)
+ {
+ return false;
+ }
+
+ // Let the container know that we're "fake" handling this event
+ renderer.NotifyFakeHandling();
+
+ return true;
+ }
+
+ public void UpdateElement(VisualElement element)
+ {
+ _isInViewCell = false;
+ _element = element;
+
+ if (_element == null)
+ {
+ return;
+ }
+
+ // Determine whether this control is inside a ViewCell;
+ // we don't fake handle the events because ListView needs them for row selection
+ var parent = _element.Parent;
+ while (parent != null)
+ {
+ if (parent is ViewCell)
+ {
+ _isInViewCell = true;
+ break;
+ }
+ parent = parent.Parent;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs b/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs
index 7f5c7f91..8f6722c3 100644
--- a/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs
@@ -92,14 +92,20 @@ namespace Xamarin.Forms.Platform.Android
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
- if (Element.InputTransparent && Element.IsEnabled)
- return false;
+ 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)
{
@@ -116,7 +122,11 @@ namespace Xamarin.Forms.Platform.Android
return handled;
}
- return _gestureDetector.Value.OnTouchEvent(e) || 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;
+
+ return handled;
}
VisualElement IVisualElementRenderer.Element => Element;
@@ -191,8 +201,6 @@ namespace Xamarin.Forms.Platform.Android
SoundEffectsEnabled = false;
}
- InputTransparent = Element.InputTransparent;
-
// must be updated AFTER SetOnClickListener is called
// SetOnClickListener implicitly calls Clickable = true
UpdateGestureRecognizers(true);
@@ -215,6 +223,7 @@ namespace Xamarin.Forms.Platform.Android
SetContentDescription();
SetFocusable();
+ UpdateInputTransparent();
Performance.Stop();
}
@@ -305,14 +314,14 @@ namespace Xamarin.Forms.Platform.Android
{
if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
UpdateBackgroundColor();
- else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
- InputTransparent = Element.InputTransparent;
else if (e.PropertyName == Accessibility.HintProperty.PropertyName)
SetContentDescription();
else if (e.PropertyName == Accessibility.NameProperty.PropertyName)
SetContentDescription();
else if (e.PropertyName == Accessibility.IsInAccessibleTreeProperty.PropertyName)
SetFocusable();
+ else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
+ UpdateInputTransparent();
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
@@ -398,6 +407,11 @@ namespace Xamarin.Forms.Platform.Android
return true;
}
+ void UpdateInputTransparent()
+ {
+ InputTransparent = Element.InputTransparent;
+ }
+
protected void SetPackager(VisualElementPackager packager)
{
_packager = packager;
diff --git a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
index 29562618..fd894e47 100644
--- a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
+++ b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
@@ -174,6 +174,7 @@
<Compile Include="AppCompat\ViewRenderer.cs" />
<Compile Include="Renderers\LocalizedDigitsKeyListener.cs" />
<Compile Include="Renderers\MasterDetailContainer.cs" />
+ <Compile Include="Renderers\MotionEventHelper.cs" />
<Compile Include="Renderers\PageContainer.cs" />
<Compile Include="Renderers\ScrollViewContainer.cs" />
<Compile Include="Renderers\StreamImagesourceHandler.cs" />