如何禁用圆形按钮裁剪区域的Pressed事件
Got it, this is a super common issue with Xamarin.Forms buttons where the visual appearance doesn't match the hit test area. Let's walk through a few solid solutions to fix this so only the circular part of your button responds to taps:
Solution 1: Custom Renderers (Native iOS/Android Implementation)
Since the native platform's button hit test is based on the control's square bounds, custom renderers let you override the touch detection logic to respect the circular shape.
iOS Custom Renderer
using Xamarin.Forms.Platform.iOS; using UIKit; using Xamarin.Forms; using CoreGraphics; [assembly: ExportRenderer(typeof(Button), typeof(CircleButtonRenderer))] namespace YourAppNamespace.iOS.Renderers { public class CircleButtonRenderer : ButtonRenderer { protected override void OnElementChanged(ElementChangedEventArgs<Button> e) { base.OnElementChanged(e); if (Control != null) { Control.ClipsToBounds = true; Control.Layer.CornerRadius = (nfloat)Element.HeightRequest / 2; // Replace default hit test with circular detection var tapGesture = new UITapGestureRecognizer(HandleTap) { ShouldReceiveTouch = (recognizer, touch) => { var touchPoint = touch.LocationInView(Control); var center = new CGPoint(Control.Bounds.Width / 2, Control.Bounds.Height / 2); var distanceFromCenter = Math.Sqrt(Math.Pow(touchPoint.X - center.X, 2) + Math.Pow(touchPoint.Y - center.Y, 2)); // Only allow taps within the circular area return distanceFromCenter <= Control.Bounds.Width / 2; } }; Control.AddGestureRecognizer(tapGesture); } } private void HandleTap(UITapGestureRecognizer recognizer) { // Trigger the button's native pressed/released/clicked events ((IButtonController)Element).SendPressed(); ((IButtonController)Element).SendReleased(); ((IButtonController)Element).SendClicked(); } } }
Android Custom Renderer
using Android.Content; using Android.Views; using Xamarin.Forms; using Xamarin.Forms.Platform.Android; [assembly: ExportRenderer(typeof(Button), typeof(CircleButtonRenderer))] namespace YourAppNamespace.Droid.Renderers { public class CircleButtonRenderer : ButtonRenderer { public CircleButtonRenderer(Context context) : base(context) { } protected override void OnElementChanged(ElementChangedEventArgs<Button> e) { base.OnElementChanged(e); if (Control != null) { Control.ClipToOutline = true; Control.SetCornerRadius((float)Element.HeightRequest / 2); } } public override bool OnTouchEvent(MotionEvent e) { if (e.Action == MotionEventActions.Down) { var centerX = Width / 2f; var centerY = Height / 2f; var touchX = e.GetX(); var touchY = e.GetY(); var distanceFromCenter = Math.Sqrt(Math.Pow(touchX - centerX, 2) + Math.Pow(touchY - centerY, 2)); // Reject taps outside the circular area if (distanceFromCenter > Width / 2f) { return false; } } return base.OnTouchEvent(e); } } }
Solution 2: Shared Code Custom Gesture Recognizer
If you want to avoid native code, you can create a custom tap gesture recognizer in your shared project that checks if the tap point falls within the circular bounds.
First, create the custom gesture recognizer:
using Xamarin.Forms; namespace YourAppNamespace { public class CircleTapGestureRecognizer : TapGestureRecognizer { public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(CircleTapGestureRecognizer), 0.0); public double CornerRadius { get => (double)GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); } protected override void OnTapped(View sender, EventArgs e) { if (sender is Button button && e is TappedEventArgs tappedArgs) { var centerX = button.Width / 2; var centerY = button.Height / 2; var tapPosition = tappedArgs.GetPosition(button); if (tapPosition.HasValue) { var distanceFromCenter = Math.Sqrt( Math.Pow(tapPosition.Value.X - centerX, 2) + Math.Pow(tapPosition.Value.Y - centerY, 2)); // Only trigger the command if tap is inside the circle if (distanceFromCenter <= CornerRadius) { base.OnTapped(sender, e); } } } } } }
Then replace your EventToCommandBehavior with this gesture in XAML:
<Button WidthRequest="300" HeightRequest="300" BackgroundColor="Red" VerticalOptions="Center" Margin="20,0,20,0" HorizontalOptions="CenterAndExpand" CornerRadius="150"> <Button.GestureRecognizers> <local:CircleTapGestureRecognizer CornerRadius="150" Command="{Binding ButtonPressedCommand}" /> </Button.GestureRecognizers> </Button>
Solution 3: ContentView with Clipping (Simplest Shared Code Approach)
Wrap your button in a ContentView that uses an EllipseGeometry for clipping. This restricts both the visual rendering and touch events to the circular area:
<ContentView WidthRequest="300" HeightRequest="300" VerticalOptions="Center" Margin="20,0,20,0" HorizontalOptions="CenterAndExpand"> <ContentView.Clip> <EllipseGeometry Center="150,150" RadiusX="150" RadiusY="150" /> </ContentView.Clip> <Button WidthRequest="300" HeightRequest="300" BackgroundColor="Red" CornerRadius="150" Command="{Binding ButtonPressedCommand}" /> </ContentView>
内容的提问来源于stack exchange,提问作者Stefanija

