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

Posted

技术标签:

【中文标题】Xamarin 表单自定义 GridView Tap 和 Long Tap 不能一起工作【英文标题】:Xamarin Forms Custom GridView Tap and Long Tap Not Working Together 【发布时间】:2019-06-17 10:50:15 【问题描述】:

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

点按有效,但长按无效。

一键关闭长按也可以。

请帮帮我。

谢谢。

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

【问题讨论】:

【参考方案1】:

我注意到您使用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。

【讨论】:

以上是关于Xamarin 表单自定义 GridView Tap 和 Long Tap 不能一起工作的主要内容,如果未能解决你的问题,请参考以下文章

xamarin 表单地图上的自定义引脚

创建自定义导航栏渲染器以在 xamarin 表单 IOS 项目中添加自定义后退按钮图标

如何自定义 xamarin 表单 UWP 顶栏

从 Xamarin 表单元素共享代码访问自定义渲染器实例

如何在 Xamarin 表单 macos 的条目(用于聊天)中添加自定义图像?

Xamarin 表单 - 如何在 TabbedView (iOS) 上自定义 VoiceOver