Xamarin.Forms - 按钮按下和释放事件

14

我希望当按钮按下和释放时,我的事件能够被触发,但在Xamarin.Forms中我只找到了点击事件。

我相信一定有一些解决方法可以实现这个功能。我基本的需求是在按钮按下时启动一个进程,在释放时停止。这似乎是一个非常基本的功能,但是目前Xamarin.Forms没有它。

我尝试了在按钮上使用TapGestureRecognizer,但是按钮仅触发了点击事件。

MyButton.Clicked += (sender, args) =>
{
  Log.V(TAG, "CLICKED");
};

var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (s, e) => {
    Log.V(TAG, "TAPPED");
};
MyButton.GestureRecognizers.Add(tapGestureRecognizer);

请记住,我需要这些事件在Android和iOS上同时正常工作。


你需要编写一个自定义按钮渲染器,以公开 Forms 掩盖的底层 TouchDown 和 TouchUp 事件。 - Jason
@jason,你能给我推荐一些好的自定义渲染器教程或示例吗? - umair.ali
https://developer.xamarin.com/guides/xamarin-forms/custom-renderer/ - Jason
谢谢 @Jason,它起作用了... - umair.ali
1
@umair.ali 请查看我的答案。 - Shimmy Weitzhandler
5个回答

24

最终我得到了@Jason建议的解决方案。下面是具体步骤:

  1. 在PCL项目中创建Xamarin.Forms.Button的子类,具有事件处理能力

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);
    }
}
  • 在各自的项目中创建特定平台的按钮渲染器

    对于安卓

  • [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();
                    }
                };
            }
        }
    }
    

    适用于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();
                };
            }
        }
    }
    
  • 在你的页面中实例化自定义按钮

  • 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");
    };
    

    希望这对某些人有所帮助 :)


    2
    它确实帮助了某个人 :) - Madhav Shenoy
    这似乎是最不方便的解决方案,用于一个简单的回调。 - clockw0rk

    7
    自从Xamarin.Forms 2.4.0版本起,PressedReleased事件已经内置(参见PR)。
    注意:为了实现对讲机效果,您可能希望使用Device.BeginInvokeOnMainThread(或通过Prism的IDeviceService)来调用连续的操作,以便触发Released事件,否则UI线程可能会被阻塞。另外,您可以将事件处理程序声明为asyncawait您的调用,以保持UI线程不被占用。

    7
    这也可以通过效果来实现,而不是完整的自定义渲染器。请参阅此文章以了解如何实现:https://alexdunn.org/2017/12/27/xamarin-tip-xamarin-forms-long-press-effect/ 如果该文章消失了,这里是您可以实现的代码:

    在共享项目中:

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

    在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;
                }
            }
        }
    }
    

    在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;
                }
            }
    
        }
    }
    

    在XAML中
    <Label Text="Long Press Me!" effects:LongPressedEffect.Command="{Binding ShowAlertCommand}" effects:LongPressedEffect.CommandParameter="{Binding .}">
        <Label.Effects>
            <effects:LongPressedEffect />
        </Label.Effects>
    </Label>
    

    2
    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();
            }
        };
    

    1
    为了在Xamarin上拦截“Pressed”和“Released”事件,我使用了“Effects”属性,如this official Guide中所述。
    使用TouchTracking.Forms甚至更简单。
    首先将库添加到您的Forms项目中(在特定于平台的项目中不需要)。
    接下来,在Xaml中按原样使用它。
    <StackLayout>
        <StackLayout.Effects>
            <tt:TouchEffect TouchAction="Handle_TouchAction" />
        </StackLayout.Effects>
        <Label Text="Sample"/>
    </StackLayout>
    

    使用tt表示:

    xmlns:tt="clr-namespace:TouchTracking.Forms;assembly=TouchTracking.Forms"
    

    最后,在后端代码中执行您的逻辑:
    void Handle_TouchAction(object sender, TouchTracking.TouchActionEventArgs args)
    {
        ;
    }
    

    Handle_TouchAction会在每次触摸操作发生时被调用,使用args.Type来区分PressedReleasedExited等动作。

    注意:Effects可用于各种组件,不仅限于StackLayout


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接