Xamarin.forms: TapGestureRecognizer Blocks Underlying View's Click Event From Firing (can't add ripple on Android)

Created on 9 Feb 2019  路  11Comments  路  Source: xamarin/Xamarin.Forms

Description

TapGestureRecognizer Blocks Underlying View's Click Event From Firing (can't add ripple on Android)

Steps to Reproduce

Take for instance this code:

<StackLayout BindableLayout.ItemsSource="{Binding Sessions}">
                            <BindableLayout.ItemTemplate>
                                <DataTemplate>
                                    <StackLayout Spacing="0" Padding="0">
                                        <StackLayout.GestureRecognizers>
                                            <TapGestureRecognizer Tapped="OnSessionTapped" />
                                        </StackLayout.GestureRecognizers>
                                        <StackLayout.Effects>
                                            <effects:RippleEffect/>
                                        </StackLayout.Effects>
                                        <local:HeaderDivider/>
                                        <local:SessionCellView FavoriteCommand="{Binding Path=BindingContext.FavoriteCommand, Source={x:Reference ContentPageFeed}}"/>
                                    </StackLayout>
                                </DataTemplate>
                            </BindableLayout.ItemTemplate>
                        </StackLayout>

with this effect on Android:

using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Widget;
using Conference.Droid;
using Android.Util;

[assembly: ExportEffect(typeof(RippleEffect), "RippleEffect")]
namespace Conference.Droid
{
    public class RippleEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            try
            {
                if (Container is Android.Views.View view)
                {
                    view.Clickable = true;
                    view.Focusable = true;

                    using (var outValue = new TypedValue())
                    {
                        view.Context.Theme.ResolveAttribute(Android.Resource.Attribute.SelectableItemBackground, outValue, true);
                        view.SetBackgroundResource(outValue.ResourceId);
                    }

                }
            }
            catch (Exception ex)
            {

            }
        }

        protected override void OnDetached()
        {

        }
    }
}


With the tap gesture recognizer enabled we do not get the click event to the underlying control so the ripple effect does not show. If I comment out the Gesture recognizer it works just fine.

See:

Expected Behavior

Click event is passed down to be handled by the platform.

Actual Behavior

It is gobbled up

Basic Information

  • Version with issue: 3.5.0
  • Last known good version: None
  • IDE: VS 2017
  • Platform Target Frameworks: Android (probably others)

    • iOS:

    • Android:

    • UWP:

  • Android Support Library Version: 28.0.0.1
  • Nuget Packages: 3.5.0
  • Affected Devices: Android

Screenshots

Reproduction Link

See: https://github.com/xamarinhq/app-conference/tree/motz/forms350

gestures 馃枛 6 high impact Android bug

Most helpful comment

Already encountered this.
The issue are triggered by the line :

Clickable = true

I'm not sure if it is a normal behavior from Android or not.

The code I used to get this effect working :
````cs
public class ViewRippleEffect : PlatformEffect
{
private static int DrawableRessourseId { get; set; }

protected override void OnAttached()
{
    if (Control == null)
    {
        return;
    }

    SetClickable();
    SetSelectedItemBackground();

    // The TapGesture doesn't work anymore when clickable is set to true
    // In this case redirect the event manually
    Control.Click += TapGestureFix;
}

protected override void OnDetached()
{
    try
    {
        Control.Click -= TapGestureFix;
    }
    catch (ObjectDisposedException)
    {
        // Sometime the object is already disposed
    }
}

private void SetClickable()
{
    Control.Clickable = true;
}

private void SetSelectedItemBackground()
{
    Context context = CurrentContext.Context;

    if (DrawableRessourseId == default(int))
    {
        TypedArray styledAttributes = context.ObtainStyledAttributes(new[] { Android.Resource.Attribute.SelectableItemBackground });

        DrawableRessourseId = styledAttributes.GetResourceId(0, 0);

        styledAttributes.Recycle();
    }

    Control.SetBackground(context.Resources.GetDrawable(DrawableRessourseId, context.Theme));
}

private void TapGestureFix(object sender, EventArgs args)
{
    Xamarin.Forms.View view = Element as Xamarin.Forms.View;

    TapGestureRecognizer tapGestureRecognizer = view
        ?.GestureRecognizers
        .OfType<TapGestureRecognizer>()
        .FirstOrDefault()
    ?? (view?.Parent as Xamarin.Forms.View)
        ?.GestureRecognizers
        .OfType<TapGestureRecognizer>()
        .FirstOrDefault();

    tapGestureRecognizer?.Command?.Execute(null);
}

}

All 11 comments

Thinking maybe workaround is to wrap the stacklayout in a stacklayout... and add the ripple to the top stacklayout and the tapgesture to the bottom........

I tried the above to wrap it and that still doesn't work :(

I'll be waiting to fix this bug

Already encountered this.
The issue are triggered by the line :

Clickable = true

I'm not sure if it is a normal behavior from Android or not.

The code I used to get this effect working :
````cs
public class ViewRippleEffect : PlatformEffect
{
private static int DrawableRessourseId { get; set; }

protected override void OnAttached()
{
    if (Control == null)
    {
        return;
    }

    SetClickable();
    SetSelectedItemBackground();

    // The TapGesture doesn't work anymore when clickable is set to true
    // In this case redirect the event manually
    Control.Click += TapGestureFix;
}

protected override void OnDetached()
{
    try
    {
        Control.Click -= TapGestureFix;
    }
    catch (ObjectDisposedException)
    {
        // Sometime the object is already disposed
    }
}

private void SetClickable()
{
    Control.Clickable = true;
}

private void SetSelectedItemBackground()
{
    Context context = CurrentContext.Context;

    if (DrawableRessourseId == default(int))
    {
        TypedArray styledAttributes = context.ObtainStyledAttributes(new[] { Android.Resource.Attribute.SelectableItemBackground });

        DrawableRessourseId = styledAttributes.GetResourceId(0, 0);

        styledAttributes.Recycle();
    }

    Control.SetBackground(context.Resources.GetDrawable(DrawableRessourseId, context.Theme));
}

private void TapGestureFix(object sender, EventArgs args)
{
    Xamarin.Forms.View view = Element as Xamarin.Forms.View;

    TapGestureRecognizer tapGestureRecognizer = view
        ?.GestureRecognizers
        .OfType<TapGestureRecognizer>()
        .FirstOrDefault()
    ?? (view?.Parent as Xamarin.Forms.View)
        ?.GestureRecognizers
        .OfType<TapGestureRecognizer>()
        .FirstOrDefault();

    tapGestureRecognizer?.Command?.Execute(null);
}

}

I don't think this is necessarily a bug but more of an area that needs enhancement. We have a percolating spec for allowing the user to specify what they want to do with the touch event. Let it keep going down the hierarchy or stop.

Let's say we "fixed" this. Then somebody will log a bug because now click events aren't being blocked by gesture recognizers

If you look at the native platforms all 3 of them handle this scenario differently. iOS, for example, if you add a gesture recognizer will block to the subview unless you add some delegate work.

So we can't just change this behavior. But we are working on exposing an api to help users decide how they want it to work.

Gotcha, yeah I think if I set the underlying to control (or one of them) to clickable then it should go down. In this case we can't get the nice ripple effect on Android :)

Similarly, TapGestureRecognizer blocks touch to parent.
https://github.com/xamarin/Xamarin.Forms/issues/6402

to further this, on iOS the stacklayout overrides the CollectionView Selection Event

```



    <StackLayout Margin="20" Padding="80" BackgroundColor="Red">
        <CollectionView ItemsSource="{Binding TestItems}" 
                        SelectionMode="Single" 
                        SelectionChangedCommand="{Binding SelectionChangedCommand}" 
                        SelectedItem="{Binding SelectedTestItem}" 
                        BackgroundColor="LightSteelBlue">
            <CollectionView.ItemsLayout>
                <ListItemsLayout Orientation="Vertical" ItemSpacing="3"/>
            </CollectionView.ItemsLayout>
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <Label BackgroundColor="Green" 
                           TextColor="White" 
                           Text="{Binding .}" 
                           HeightRequest="40" 
                           HorizontalTextAlignment="Center" 
                           VerticalTextAlignment="Center"/>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>

    </StackLayout>
</StackLayout>

```

if you run this code the CollectionView SelecteItem isn't changed and the SelectionChagnedCommand isn't called. All that is called is the TapGestureRecognizer

Hello,

Any update on this ? Adding a clickable view disable any pangesture listener I have higher in the hierarchy too.
Do someone know a workaround ?

Thanks !

hi @samhouts ,

Any update on this ?

Hi @samhouts ,

any update on this ?

Was this page helpful?
0 / 5 - 0 ratings