xamarin / Xamarin.Forms

Xamarin.Forms is no longer supported. Migrate your apps to .NET MAUI.
https://aka.ms/xamarin-upgrade
Other
5.63k stars 1.87k forks source link

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

Open jamesmontemagno opened 5 years ago

jamesmontemagno commented 5 years ago

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

Screenshots

Reproduction Link

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

jamesmontemagno commented 5 years ago

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........

jamesmontemagno commented 5 years ago

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

lucasriechelmann commented 5 years ago

I'll be waiting to fix this bug

kvpt commented 5 years ago

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 :


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);
    }
}
PureWeen commented 5 years ago

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.

jamesmontemagno commented 5 years ago

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 :)

syncsiva commented 5 years ago

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

hjharvey-MSFT commented 4 years ago

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

<StackLayout BackgroundColor="Green">
        <StackLayout.GestureRecognizers>
            <TapGestureRecognizer Command="{Binding StackTappedCommand}"/>
        </StackLayout.GestureRecognizers>

        <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

jonathanantoine commented 4 years ago

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 !

KarthikRaja26 commented 4 years ago

hi @samhouts ,

Any update on this ?

KarthikRaja26 commented 3 years ago

Hi @samhouts ,

any update on this ?