Xamarin.Forms - Button Pressed & Released Event
Finally I got the solution suggested by @Jason. Here we go...
Create sub class of Xamarin.Forms.Button in PCL project, with event handling capability
public class CustomButton : Button { public event EventHandler Pressed; public event EventHandler Released; public virtual void OnPressed() { Pressed?.Invoke(this, EventArgs.Empty); } public virtual void OnReleased() { Released?.Invoke(this, EventArgs.Empty); } }
Create platform specific button renderer in respective project
For Andorid
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))] namespace WalkieTalkie.Droid.Renderer { public class CustomButtonRenderer : ButtonRenderer { protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e) { base.OnElementChanged(e); var customButton = e.NewElement as CustomButton; var thisButton = Control as Android.Widget.Button; thisButton.Touch += (object sender, TouchEventArgs args) => { if (args.Event.Action == MotionEventActions.Down) { customButton.OnPressed(); } else if (args.Event.Action == MotionEventActions.Up) { customButton.OnReleased(); } }; } } }
For IOS
[assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonRenderer))] namespace WalkieTalkie.iOS.Renderer { public class CustomButtonRenderer : ButtonRenderer { protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e) { base.OnElementChanged(e); var customButton = e.NewElement as CustomButton; var thisButton = Control as UIButton; thisButton.TouchDown += delegate { customButton.OnPressed(); }; thisButton.TouchUpInside += delegate { customButton.OnReleased(); }; } } }
Instantiate your custom button in your page
var myButton = new CustomButton { Text = "CustomButton", HorizontalOptions = LayoutOptions.FillAndExpand }; myButton.Pressed += (sender, args) => { System.Diagnostics.Debug.WriteLine("Pressed"); }; myButton.Released += (sender, args) => { System.Diagnostics.Debug.WriteLine("Pressed"); };
Hope this help someone :)
Button button = FindViewById (Resource.Id.myButton);
button.Touch += (object sender, View.TouchEventArgs e) =>
{
if (e.Event.Action == MotionEventActions.Up)
{
Toast.MakeText(this, "Key Up", ToastLength.Short).Show();
}
if(e.Event.Action == MotionEventActions.Down)
{
Toast.MakeText(this, "Key Down", ToastLength.Short).Show();
}
};
This can also be done with an effect instead of a full fledged custom renderer. See this post for an explanation of how to do it:
https://alexdunn.org/2017/12/27/xamarin-tip-xamarin-forms-long-press-effect/
In case that post ever goes away, here is the code that you can implement:
In shared project:
/// <summary>
/// Long pressed effect. Used for invoking commands on long press detection cross platform
/// </summary>
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)
{
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);
}
}
In Android:
[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(AndroidLongPressedEffect), "LongPressedEffect")]
namespace AndroidAppNamespace.Effects
{
/// <summary>
/// Android long pressed effect.
/// </summary>
public class AndroidLongPressedEffect : PlatformEffect
{
private bool _attached;
/// <summary>
/// Initializer to avoid linking out
/// </summary>
public static void Initialize() { }
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Yukon.Application.AndroidComponents.Effects.AndroidLongPressedEffect"/> class.
/// Empty constructor required for the odd Xamarin.Forms reflection constructor search
/// </summary>
public AndroidLongPressedEffect()
{
}
/// <summary>
/// Apply the handler
/// </summary>
protected override void OnAttached()
{
//because an effect can be detached immediately after attached (happens in listview), only attach the handler one time.
if (!_attached)
{
if (Control != null)
{
Control.LongClickable = true;
Control.LongClick += Control_LongClick;
}
else
{
Container.LongClickable = true;
Container.LongClick += Control_LongClick;
}
_attached = true;
}
}
/// <summary>
/// Invoke the command if there is one
/// </summary>
/// <param name="sender">Sender.</param>
/// <param name="e">E.</param>
private void Control_LongClick(object sender, Android.Views.View.LongClickEventArgs e)
{
Console.WriteLine("Invoking long click command");
var command = LongPressedEffect.GetCommand(Element);
command?.Execute(LongPressedEffect.GetCommandParameter(Element));
}
/// <summary>
/// Clean the event handler on detach
/// </summary>
protected override void OnDetached()
{
if (_attached)
{
if (Control != null)
{
Control.LongClickable = true;
Control.LongClick -= Control_LongClick;
}
else
{
Container.LongClickable = true;
Container.LongClick -= Control_LongClick;
}
_attached = false;
}
}
}
}
In iOS:
[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(iOSLongPressedEffect), "LongPressedEffect")]
namespace iOSNamespace.Effects
{
/// <summary>
/// iOS long pressed effect
/// </summary>
public class iOSLongPressedEffect : PlatformEffect
{
private bool _attached;
private readonly UILongPressGestureRecognizer _longPressRecognizer;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Yukon.Application.iOSComponents.Effects.iOSLongPressedEffect"/> class.
/// </summary>
public iOSLongPressedEffect()
{
_longPressRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
}
/// <summary>
/// Apply the handler
/// </summary>
protected override void OnAttached()
{
//because an effect can be detached immediately after attached (happens in listview), only attach the handler one time
if (!_attached)
{
Container.AddGestureRecognizer(_longPressRecognizer);
_attached = true;
}
}
/// <summary>
/// Invoke the command if there is one
/// </summary>
private void HandleLongClick()
{
var command = LongPressedEffect.GetCommand(Element);
command?.Execute(LongPressedEffect.GetCommandParameter(Element));
}
/// <summary>
/// Clean the event handler on detach
/// </summary>
protected override void OnDetached()
{
if (_attached)
{
Container.RemoveGestureRecognizer(_longPressRecognizer);
_attached = false;
}
}
}
}
In XAML
<Label Text="Long Press Me!" effects:LongPressedEffect.Command="{Binding ShowAlertCommand}" effects:LongPressedEffect.CommandParameter="{Binding .}">
<Label.Effects>
<effects:LongPressedEffect />
</Label.Effects>
</Label>
Since Xamarin.Forms 2.4.0, The events Pressed
and Released
are offered out of the box (see PR).
Note: in order to achieve a Walkie Talkie effect you might want to use Device.BeginInvokeOnMainThread
(or via Prism's IDeviceService
) to invoke consequent actions so the Released
event will get called, otherwise the UI thread might be blocked.
Alternatively, you can declare the event handlers as async
and await
your invocations, to keep the UI thread unoccupied.