I\'m trying to add a custom content to a button in Xamarin Forms.
By default Button is created like this:
Here is my implementation of a button with content, that has some additional things to better mimic an actual button:
ICommand Command
object CommandParameter
Clicked
Pressed
Released
VisuallyPressedChanged
(Occurs when the style of the button should be changed to let the user know that the button has been pressed)It uses TouchTracking.Forms package for handling touch events, you can download it using NuGet.
public class ContentButton : ContentView
{
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(ContentButton), null, BindingMode.Default);
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ContentButton));
///
/// Occurs when the Button is clicked.
///
public event EventHandler Clicked;
///
/// Occurs when the Button is pressed.
///
public event EventHandler Pressed;
///
/// Occurs when the Button is released.
/// The released event always occur before the clicked event.
///
public event EventHandler Released;
///
/// Occurs when the style of the button should be changed to let the user know that the button has been pressed.
/// If the argument is true, it means that the Button was just pressed.
/// If the argument is false, it means that the Button was just released or that the user has moved his finger out of the buttons boundaries.
///
public event EventHandler VisuallyPressedChanged;
///
/// Gets or sets the command to invoke when the button is activated. This is a bindable property.
///
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
///
/// Gets or sets the parameter to pass to the Command property. This is a bindable property.
///
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
private bool isVisuallyPressed;
public ContentButton()
{
var touchEffect = new TouchEffect
{
Capture = true
};
touchEffect.TouchAction += TouchEffect_TouchAction;
Effects.Add(touchEffect);
}
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
// so that the touch events are ignored and bypassed to this control
if(child is VisualElement visualChild) {
visualChild.InputTransparent = true;
}
}
private long? currentId;
private object touchEffect_lock = new object();
private void TouchEffect_TouchAction(object sender, TouchActionEventArgs e)
{
// only track one touch
if(currentId != e.Id && e.Type!=TouchActionType.Pressed) {
return;
}
lock(touchEffect_lock) {
switch(e.Type) {
case TouchActionType.Pressed:
currentId = e.Id;
Pressed?.Invoke(this, EventArgs.Empty);
isVisuallyPressed = true;
VisuallyPressedChanged?.Invoke(this, true);
break;
case TouchActionType.Moved:
if(isVisuallyPressed) {
bool isInside = e.Location.X >= 0 && e.Location.Y >= 0 && e.Location.X <= Bounds.Width && e.Location.Y <= Bounds.Height;
if(!isInside) {
isVisuallyPressed = false;
VisuallyPressedChanged?.Invoke(this, false);
}
}
break;
case TouchActionType.Cancelled:
currentId = null;
isVisuallyPressed = false;
VisuallyPressedChanged?.Invoke(this, false);
break;
case TouchActionType.Released:
currentId = null;
if(isVisuallyPressed) {
Released?.Invoke(this, EventArgs.Empty);
Clicked?.Invoke(this, EventArgs.Empty);
if(Command != null && Command.CanExecute(CommandParameter)) {
Command.Execute(CommandParameter);
}
isVisuallyPressed = false;
VisuallyPressedChanged?.Invoke(this, false);
}
break;
}
}
}
}