Xamarin Forms 自定义 GridView Tap 和 Long Tap 不能一起工作

Xamarin Forms Custom GridView Tap and Long Tap Not Working Together

我想将点击和长按与自定义网格视图一起使用。

点击有效,但长按无效。

一键关闭长按也有效

请帮帮我

谢谢。

public class GridView : Grid
{
    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(GridView), default(IList), BindingMode.TwoWay);
    public static readonly BindableProperty ItemTappedCommandProperty = BindableProperty.Create(nameof(ItemTappedCommand), typeof(ICommand), typeof(GridView), null);

    public static readonly BindableProperty ItemLongTappedCommandProperty = BindableProperty.Create(nameof(ItemLongTappedCommand), typeof(ICommand), typeof(GridView), null);

    public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(GridView), default(DataTemplate));
    public static readonly BindableProperty MaxColumnsProperty = BindableProperty.Create(nameof(MaxColumns), typeof(int), typeof(GridView), 2);
    public static readonly BindableProperty TileHeightProperty = BindableProperty.Create(nameof(TileHeight), typeof(float), typeof(GridView), 220f);//adjusted here reuired height

    public GridView()
    {
        PropertyChanged += GridView_PropertyChanged;
        PropertyChanging += GridView_PropertyChanging;
    }

    public IList ItemsSource
    {
        get { return (IList)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public ICommand ItemTappedCommand
    {
        get { return (ICommand)GetValue(ItemTappedCommandProperty); }
        set { SetValue(ItemTappedCommandProperty, value); }
    }

    public ICommand ItemLongTappedCommand
    {
        get { return (ICommand)GetValue(ItemLongTappedCommandProperty); }
        set { SetValue(ItemLongTappedCommandProperty, value); }
    }

    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate)GetValue(ItemTemplateProperty); }
        set { SetValue(ItemTemplateProperty, value); }
    }

    public int MaxColumns
    {
        get { return (int)GetValue(MaxColumnsProperty); }
        set { SetValue(MaxColumnsProperty, value); }
    }

    public float TileHeight
    {
        get { return (float)GetValue(TileHeightProperty); }
        set { SetValue(TileHeightProperty, value); }
    }

    private void BuildColumns()
    {
        ColumnDefinitions.Clear();
        for (var i = 0; i < MaxColumns; i++)
        {
            ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
        }
    }

    private View BuildTile(object item1)
    {
        var template = ItemTemplate.CreateContent() as View;
        template.BindingContext = item1;

        if (ItemTappedCommand != null)
        {
            var tapGestureRecognizer = new TapGestureRecognizer
            {
                Command = ItemTappedCommand,
                CommandParameter = item1,
            };

            template.GestureRecognizers.Add(tapGestureRecognizer);
        }

        // Tap komutu eziyor.
        if (ItemLongTappedCommand != null)
        {
            template.Effects.Add(new LongPressedEffect());
            LongPressedEffect.SetCommand(template, ItemLongTappedCommand);
            //LongPressedEffect.SetCommandParameter(template, item1);
        }

        return template;
    }

    private void BuildTiles()
    {
        // Wipe out the previous row & Column definitions if they're there.
        if (RowDefinitions.Any())
        {
            RowDefinitions.Clear();
        }

        BuildColumns();
        Children.Clear();
        var tiles = ItemsSource;
        if (tiles != null)
        {
            var numberOfRows = Math.Ceiling(tiles.Count / (float)MaxColumns);
            for (var i = 0; i < numberOfRows; i++)
            {
                RowDefinitions.Add(new RowDefinition { Height = new GridLength(0, GridUnitType.Auto) });
            }

            for (var index = 0; index < tiles.Count; index++)
            {
                var column = index % MaxColumns;
                var row = (int)Math.Floor(index / (float)MaxColumns);
                var tile = BuildTile(tiles[index]);
                Children.Add(tile, column, row);
            }
        }
    }

    private void GridView_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == ItemsSourceProperty.PropertyName)
        {
            var items = ItemsSource as INotifyCollectionChanged;
            if (items != null)
                items.CollectionChanged += ItemsCollectionChanged;
            BuildTiles();
        }

        if (e.PropertyName == MaxColumnsProperty.PropertyName || e.PropertyName == TileHeightProperty.PropertyName)
        {
            BuildTiles();
        }
    }

    private void GridView_PropertyChanging(object sender, Xamarin.Forms.PropertyChangingEventArgs e)
    {
        if (e.PropertyName == ItemsSourceProperty.PropertyName)
        {
            var items = ItemsSource as INotifyCollectionChanged;
            if (items != null)
                items.CollectionChanged -= ItemsCollectionChanged;
        }
    }

    private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        BuildTiles();
    }
}


public class LongPressedEffect : RoutingEffect
{
    public LongPressedEffect() : base("MyApp.LongPressedEffect")
    { }

    public static readonly BindableProperty CommandProperty = BindableProperty.CreateAttached("Command", typeof(ICommand), typeof(LongPressedEffect), (object)null);
    public static ICommand GetCommand(BindableObject view)
    {
        //do something you want 
        Console.WriteLine("long press Gesture recognizer has been striked");

        return (ICommand)view.GetValue(CommandProperty);
    }

    public static void SetCommand(BindableObject view, ICommand value)
    {
        view.SetValue(CommandProperty, value);
    }


    public static readonly BindableProperty CommandParameterProperty = BindableProperty.CreateAttached("CommandParameter", typeof(object), typeof(LongPressedEffect), (object)null);
    public static object GetCommandParameter(BindableObject view)
    {
        return view.GetValue(CommandParameterProperty);
    }

    public static void SetCommandParameter(BindableObject view, object value)
    {
        view.SetValue(CommandParameterProperty, value);
    }
}

我注意到您使用 Effect 创建了自己的长按命令。但是如果同时消耗一个TapGestureRecognizer,就会拦截效果。那么你长按的命令将不会被触发。

您可以在效果中定义单击事件来实现它们。这是我的效果:

public class PressedEffect : RoutingEffect
{
    public PressedEffect() : base("MyApp.PressedEffect")
    {
    }

    public static readonly BindableProperty LongTapCommandProperty = BindableProperty.CreateAttached("LongTapCommand", typeof(ICommand), typeof(PressedEffect), (object)null);
    public static ICommand GetLongTapCommand(BindableObject view)
    {
        return (ICommand)view.GetValue(LongTapCommandProperty);
    }

    public static void SetLongTapCommand(BindableObject view, ICommand value)
    {
        view.SetValue(LongTapCommandProperty, value);
    }


    public static readonly BindableProperty LongParameterProperty = BindableProperty.CreateAttached("LongParameter", typeof(object), typeof(PressedEffect), (object)null);
    public static object GetLongParameter(BindableObject view)
    {
        return view.GetValue(LongParameterProperty);
    }

    public static void SetLongParameter(BindableObject view, object value)
    {
        view.SetValue(LongParameterProperty, value);
    }

    public static readonly BindableProperty TapCommandProperty = BindableProperty.CreateAttached("TapCommand", typeof(ICommand), typeof(PressedEffect), (object)null);
    public static ICommand GetTapCommand(BindableObject view)
    {
        return (ICommand)view.GetValue(TapCommandProperty);
    }

    public static void SetTapCommand(BindableObject view, ICommand value)
    {
        view.SetValue(TapCommandProperty, value);
    }

    public static readonly BindableProperty TapParameterProperty = BindableProperty.CreateAttached("TapParameter", typeof(object), typeof(PressedEffect), (object)null);
    public static object GetTapParameter(BindableObject view)
    {
        return view.GetValue(TapParameterProperty);
    }

    public static void SetTapParameter(BindableObject view, object value)
    {
        view.SetValue(TapParameterProperty, value);
    }
}

Android 实施:

[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(AndroidPressedEffect), "PressedEffect")]
namespace PressedEffectDemo.Droid
{
    public class AndroidPressedEffect : PlatformEffect
    {
        private bool _attached;

        public static void Initialize() { }

        public AndroidPressedEffect()
        {
        }

        protected override void OnAttached()
        {
            if (!_attached)
            {
                if (Control != null)
                {
                    Control.LongClickable = true;
                    Control.LongClick += Control_LongClick;
                    Control.Click += Control_Click;
                }
                else
                {
                    Container.LongClickable = true;
                    Container.LongClick += Control_LongClick;
                    Container.Click += Control_Click;
                }
                _attached = true;
            }
        }

        private void Control_Click(object sender, EventArgs e)
        {
            var command = PressedEffect.GetTapCommand(Element);
            command?.Execute(PressedEffect.GetTapParameter(Element));
        }

        private void Control_LongClick(object sender, Android.Views.View.LongClickEventArgs e)
        {
            var command = PressedEffect.GetLongTapCommand(Element);
            command?.Execute(PressedEffect.GetLongParameter(Element));
        }

        protected override void OnDetached()
        {
            if (_attached)
            {
                if (Control != null)
                {
                    Control.LongClickable = true;
                    Control.LongClick -= Control_LongClick;
                    Control.Click -= Control_Click;
                }
                else
                {
                    Container.LongClickable = true;
                    Container.LongClick -= Control_LongClick;
                    Control.Click -= Control_Click;
                }
                _attached = false;
            }
        }
    }
}

iOS 实施:

[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(iOSPressedEffect), "PressedEffect")]
namespace PressedEffectDemo.iOS
{
    public class iOSPressedEffect : PlatformEffect
    {
        private bool _attached;
        private readonly UILongPressGestureRecognizer _longPressRecognizer;
        private readonly UITapGestureRecognizer _tapRecognizer;
        public iOSPressedEffect()
        {
            _longPressRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
            _tapRecognizer = new UITapGestureRecognizer(HandleClick);
        }

        protected override void OnAttached()
        {
            if (!_attached)
            {
                Container.AddGestureRecognizer(_longPressRecognizer);
                Container.AddGestureRecognizer(_tapRecognizer);
                _attached = true;
            }
        }

        private void HandleClick()
        {
            var command = PressedEffect.GetTapCommand(Element);
            command?.Execute(PressedEffect.GetTapParameter(Element));
        }
        private void HandleLongClick(UILongPressGestureRecognizer recognizer)
        {
            if (recognizer.State == UIGestureRecognizerState.Ended)
            {
                var command = PressedEffect.GetLongTapCommand(Element);
                command?.Execute(PressedEffect.GetLongParameter(Element));
            }           
        }

        protected override void OnDetached()
        {
            if (_attached)
            {
                Container.RemoveGestureRecognizer(_longPressRecognizer);
                Container.RemoveGestureRecognizer(_tapRecognizer);
                _attached = false;
            }
        }
    }
}

最后,您可以在 XAML 上消费它们,例如:

<StackLayout>        
    <Grid HeightRequest="200" 
            BackgroundColor="Green" 
            local:PressedEffect.TapCommand="{Binding TapCommand}" 
            local:PressedEffect.LongTapCommand="{Binding LongTapCommand}">
        <Grid.Effects>
            <local:PressedEffect />
        </Grid.Effects>
    </Grid>
</StackLayout>

你可以参考我的示例here