Reputation: 2714
I want to my event to trigger when button pressed and released, but I can only find Click event in Xamarin.Forms.
I believe there must be some work around to get this functionality. My basic need is to start a process when button is pressed and stop when released. It seems to be a very basic feature but Xamarin.Forms doesn't have it right now.
I tried TapGestureRecognizer on button, but button is firing only click event.
MyButton.Clicked += (sender, args) =>
{
Log.V(TAG, "CLICKED");
};
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (s, e) => {
Log.V(TAG, "TAPPED");
};
MyButton.GestureRecognizers.Add(tapGestureRecognizer);
Keep in mind that I need those events to be working in Android and iOS togather.
Upvotes: 14
Views: 24469
Reputation: 1441
In order to intercept Pressed
and Released
Event on Xamarin I've used Effects
property as explained in this official Guide.
It's even more simple using TouchTracking.Forms.
First add the library to your Forms
project (not needed in platform specific project).
Next use it in Xaml asis
<StackLayout>
<StackLayout.Effects>
<tt:TouchEffect TouchAction="Handle_TouchAction" />
</StackLayout.Effects>
<Label Text="Sample"/>
</StackLayout>
With tt
refering to :
xmlns:tt="clr-namespace:TouchTracking.Forms;assembly=TouchTracking.Forms"
Finally do your logic in code behind :
void Handle_TouchAction(object sender, TouchTracking.TouchActionEventArgs args)
{
;
}
Handle_TouchAction
will be called everytime a touch action occurs, use args.Type
to distinguish the action Pressed
, Released
,Exited
...
N.B Effects
is available for various components not only StackLayout
.
Upvotes: 1
Reputation: 104741
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.
Upvotes: 7
Reputation: 1900
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>
Upvotes: 7
Reputation: 831
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();
}
};
Upvotes: 2
Reputation: 2714
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 :)
Upvotes: 24