How to long press using .NET MAUI

1.3k Views Asked by At

I want to know if there is any well-known library or an approach to code a long press in .NET MAUI. I'm aware of Mr.Gestures library which is capable of long pressing but don't want to purchase a license for that.

I want to use the long pressing effect so that when a user presses for a long time DisplayActionSheet activates showing the user some options. I found this link about CollectionView LongPress but I'm not using CollectionView so I don't think it applies to my code. Currently, I am using ListView and want to implement a long press method to each row but am stuck with it. If there is no built-in library for NET MAUI is creating platform-specific code or using Xamarin Forms library the only way to do it?

1

There are 1 best solutions below

2
FreakyAli On

UPDATE

The effects are now out on GitHub checkout release v0.1.1-pre and above for the same!

Answer

I am planning to add a Ripple/Fade and Longpress effect into FreakyEffects.

The latest PR is here.

It has a Longpress command you can use, I am adding the relevant code here you can later add FreakyEffects if you want.

If you don't mind waiting this would be out of Nuget by the end of this month.

Create a RoutingEffect:

public class CommandsRoutingEffect : RoutingEffect
{
}

Its Android Platform Effect:

public class CommandsPlatform : PlatformEffect
{
    public View View => Control ?? Container;
    public bool IsDisposed => (Container as IVisualElementRenderer)?.Element == null;

    DateTime _tapTime;
    readonly Rect _rect = new Rect();
    readonly int[] _location = new int[2];

    public static void Init()
    {
    }

    protected override void OnAttached()
    {
        View.Clickable = true;
        View.LongClickable = true;
        View.SoundEffectsEnabled = true;
        TouchCollector.Add(View, OnTouch);
    }

    void OnTouch(View.TouchEventArgs args)
    {
        switch (args.Event.Action)
        {
            case MotionEventActions.Down:
                _tapTime = DateTime.Now;
                break;

            case MotionEventActions.Up:
                if (IsViewInBounds((int)args.Event.RawX, (int)args.Event.RawY))
                {
                    var range = (DateTime.Now - _tapTime).TotalMilliseconds;
                    if (range > 800)
                        LongClickHandler();
                    else
                        ClickHandler();
                }
                break;
        }
    }

    bool IsViewInBounds(int x, int y)
    {
        View.GetDrawingRect(_rect);
        View.GetLocationOnScreen(_location);
        _rect.Offset(_location[0], _location[1]);
        return _rect.Contains(x, y);
    }

    void ClickHandler()
    {
        var cmd = Commands.GetTap(Element);
        var param = Commands.GetTapParameter(Element);
        if (cmd?.CanExecute(param) ?? false)
            cmd.Execute(param);
    }

    void LongClickHandler()
    {
        var cmd = Commands.GetLongTap(Element);

        if (cmd == null)
        {
            ClickHandler();
            return;
        }

        var param = Commands.GetLongTapParameter(Element);
        if (cmd.CanExecute(param))
            cmd.Execute(param);
    }

    protected override void OnDetached()
    {
        if (IsDisposed) return;
        TouchCollector.Delete(View, OnTouch);
    }
}

iOS version of the same:

public class CommandsPlatform : PlatformEffect
{
    public UIView View => Control ?? Container;

    DateTime _tapTime;
    ICommand _tapCommand;
    ICommand _longCommand;
    object _tapParameter;
    object _longParameter;

    protected override void OnAttached()
    {
        View.UserInteractionEnabled = true;

        UpdateTap();
        UpdateTapParameter();
        UpdateLongTap();
        UpdateLongTapParameter();

        TouchGestureCollector.Add(View, OnTouch);
    }

    protected override void OnDetached()
    {
        TouchGestureCollector.Delete(View, OnTouch);
    }

    void OnTouch(TouchGestureRecognizer.TouchArgs e)
    {
        switch (e.State)
        {
            case TouchGestureRecognizer.TouchState.Started:
                _tapTime = DateTime.Now;
                break;

            case TouchGestureRecognizer.TouchState.Ended:
                if (e.Inside)
                {
                    var range = (DateTime.Now - _tapTime).TotalMilliseconds;
                    if (range > 800)
                        LongClickHandler();
                    else
                        ClickHandler();
                }
                break;

            case TouchGestureRecognizer.TouchState.Cancelled:
                break;
        }
    }

    void ClickHandler()
    {
        if (_tapCommand?.CanExecute(_tapParameter) ?? false)
            _tapCommand.Execute(_tapParameter);
    }

    void LongClickHandler()
    {
        if (_longCommand == null)
            ClickHandler();
        else if (_longCommand.CanExecute(_longParameter))
            _longCommand.Execute(_longParameter);
    }

    protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
    {
        base.OnElementPropertyChanged(args);

        if (args.PropertyName == Commands.TapProperty.PropertyName)
            UpdateTap();
        else if (args.PropertyName == Commands.TapParameterProperty.PropertyName)
            UpdateTapParameter();
        else if (args.PropertyName == Commands.LongTapProperty.PropertyName)
            UpdateLongTap();
        else if (args.PropertyName == Commands.LongTapParameterProperty.PropertyName)
            UpdateLongTapParameter();
    }

    void UpdateTap()
    {
        _tapCommand = Commands.GetTap(Element);
    }

    void UpdateTapParameter()
    {
        _tapParameter = Commands.GetTapParameter(Element);
    }

    void UpdateLongTap()
    {
        _longCommand = Commands.GetLongTap(Element);
    }

    void UpdateLongTapParameter()
    {
        _longParameter = Commands.GetLongTapParameter(Element);
    }

    public static void Init()
    {
    }
}

Then create a static class that can help you use it:

public static class Commands
{
    [Obsolete("Not need with usual Linking")]
    public static void Init()
    {
    }

    public static readonly BindableProperty TapProperty =
        BindableProperty.CreateAttached(
            "Tap",
            typeof(ICommand),
            typeof(Commands),
            default(ICommand),
            propertyChanged: PropertyChanged
        );

    public static void SetTap(BindableObject view, ICommand value)
    {
        view.SetValue(TapProperty, value);
    }

    public static ICommand GetTap(BindableObject view)
    {
        return (ICommand)view.GetValue(TapProperty);
    }

    public static readonly BindableProperty TapParameterProperty =
        BindableProperty.CreateAttached(
            "TapParameter",
            typeof(object),
            typeof(Commands),
            default(object),
            propertyChanged: PropertyChanged
        );

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

    public static object GetTapParameter(BindableObject view)
    {
        return view.GetValue(TapParameterProperty);
    }

    public static readonly BindableProperty LongTapProperty =
        BindableProperty.CreateAttached(
            "LongTap",
            typeof(ICommand),
            typeof(Commands),
            default(ICommand),
            propertyChanged: PropertyChanged
        );

    public static void SetLongTap(BindableObject view, ICommand value)
    {
        view.SetValue(LongTapProperty, value);
    }

    public static ICommand GetLongTap(BindableObject view)
    {
        return (ICommand)view.GetValue(LongTapProperty);
    }

    public static readonly BindableProperty LongTapParameterProperty =
        BindableProperty.CreateAttached(
            "LongTapParameter",
            typeof(object),
            typeof(Commands),
            default(object)
        );

    public static void SetLongTapParameter(BindableObject view, object value)
    {
        view.SetValue(LongTapParameterProperty, value);
    }

    public static object GetLongTapParameter(BindableObject view)
    {
        return view.GetValue(LongTapParameterProperty);
    }

    static void PropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (!(bindable is View view))
            return;

        var eff = view.Effects.FirstOrDefault(e => e is CommandsRoutingEffect);

        if (GetTap(bindable) != null || GetLongTap(bindable) != null)
        {
            view.InputTransparent = false;

            if (eff != null) return;
            view.Effects.Add(new CommandsRoutingEffect());
            if (EffectsConfig.AutoChildrenInputTransparent && bindable is Layout &&
                !EffectsConfig.GetChildrenInputTransparent(view))
            {
                EffectsConfig.SetChildrenInputTransparent(view, true);
            }
        }
        else
        {
            if (eff == null || view.BindingContext == null) return;
            view.Effects.Remove(eff);
            if (EffectsConfig.AutoChildrenInputTransparent && bindable is Layout &&
                EffectsConfig.GetChildrenInputTransparent(view))
            {
                EffectsConfig.SetChildrenInputTransparent(view, false);
            }
        }
    }
}

Register this:

   public static void RegisterEffect(this IEffectsBuilder effects)
    {
        effects.Add<CommandsRoutingEffect, CommandsPlatform>();
    }

In your view, you can use Commands.LongTap and assign the relevant command.

If I am missing some classes you can get them from Github.