如何使用 Xamarin Forms 为 iOS 项目制作可点击的滑块?
How to make clickable slider for iOS project with Xamarin Forms?
我使用滑块来制作星级评分功能。为此,我将滑块放在背景中以接收手指的位置。这在 android 上工作得很好,因为每当你触摸滑块时它都会改变它的值。 iOS 上的 UISlider 仅在您触摸其拇指时触发事件。
我觉得,可以这样做。
1.Add 点击手势到整个滑块控件
UILongPressGestureRecognizer uiTap = new UILongPressGestureRecognizer(Tapped);
uiTap.MinimumPressDuration = 0;
AddGestureRecognizer(uiTap);
2。从触摸读取坐标并将它们传递给控件。
但是我不知道该怎么做。
我的 xaml 看起来像这样。
<Grid>
<Slider/>
<Grid>
<!-- here is my stars -->
</Grid>
</Grid>
这里有很好的答案,代码在 objective-c:
好的,我能够将 swift 上的代码从 this answer 调整为 C# 代码,所以现在一切都按预期工作。
这是我的代码:
using MyProject.iOS.Renderers;
using CoreGraphics;
using Foundation;
using System;
using System.Linq;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Slider), typeof(ClickableSliderRenderer))]
namespace MyProject.iOS.Renderers
{
public class ClickableSliderRenderer : SliderRenderer
{
UILongPressGestureRecognizer uiTap;
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
base.OnElementChanged(e);
uiTap = new UILongPressGestureRecognizer(Tapped);
uiTap.MinimumPressDuration = 0;
AddGestureRecognizer(uiTap);
}
private void Tapped(object sender)
{
CGPoint point = uiTap.LocationInView(this);
nfloat thumbWidth = Control.Subviews.LastOrDefault().Bounds.Size.Width;
nfloat value;
if (point.X <= thumbWidth / 2.0)
value = Control.MinValue;
else if (point.X >= Control.Bounds.Size.Width - thumbWidth / 2)
value = Control.MaxValue;
else
{
var percentage = ((point.X - thumbWidth / 2) / (Control.Bounds.Size.Width - thumbWidth));
var delta = percentage * (Control.MaxValue - Control.MinValue);
value = Control.MinValue + delta;
}
if (uiTap.State == UIGestureRecognizerState.Began)
{
UIView.Animate(0.35, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animation:
() =>
{
Control.SetValue((float)value, true);
Control.SendActionForControlEvents(UIControlEvent.ValueChanged);
}, completion: null);
}
else if (uiTap.State == UIGestureRecognizerState.Changed)
{
Control.SetValue((float)value, false);
Control.SendActionForControlEvents(UIControlEvent.ValueChanged);
}
else
{
Control.SetValue((float)value, false);
}
Control.ThumbRectForBounds(Bounds, Bounds, (float)value);
}
}
}
我在 Xamarin iOS C#
中创建了一个类似的解决方案
(改编自此answer)
[Register(nameof(UISliderCustom))]
[DesignTimeVisible(true)]
public class UISliderCustom : UISlider
{
public UISliderCustom(IntPtr handle) : base(handle) { }
public UISliderCustom()
{
// Called when created from code.
Initialize();
}
public override void AwakeFromNib()
{
// Called when loaded from xib or storyboard.
Initialize();
}
void Initialize()
{
// Common initialization code here.
var longPress = new UILongPressGestureRecognizer(tapAndSlide);
longPress.MinimumPressDuration = 0;
//longPress.CancelsTouchesInView = false;
this.AddGestureRecognizer(longPress);
this.UserInteractionEnabled = true;
}
private void tapAndSlide(UILongPressGestureRecognizer gesture)
{
System.Diagnostics.Debug.WriteLine($"{nameof(UISliderCustom)} RecognizerState {gesture.State}");
// need to propagate events down the chain
// I imagine iOS does something similar
// for whatever recogniser on the thumb control
// It's not enough to set CancelsTouchesInView because
// if clicking on the track away from the thumb control
// the thumb gesture recogniser won't pick it up anyway
switch (gesture.State)
{
case UIGestureRecognizerState.Cancelled:
this.SendActionForControlEvents(UIControlEvent.TouchCancel);
break;
case UIGestureRecognizerState.Began:
this.SendActionForControlEvents(UIControlEvent.TouchDown);
break;
case UIGestureRecognizerState.Changed:
this.SendActionForControlEvents(UIControlEvent.ValueChanged);
break;
case UIGestureRecognizerState.Ended:
this.SendActionForControlEvents(UIControlEvent.TouchUpInside);
break;
case UIGestureRecognizerState.Failed:
//?
break;
case UIGestureRecognizerState.Possible:
//?
break;
}
var pt = gesture.LocationInView(this);
var thumbWidth = CurrentThumbImage.Size.Width;
var value = 0f;
if (pt.X <= thumbWidth / 2)
{
value = this.MinValue;
}
else if (pt.X >= this.Bounds.Size.Width - thumbWidth / 2)
{
value = this.MaxValue;
}
else
{
var percentage = ((pt.X - thumbWidth / 2) / (this.Bounds.Size.Width - thumbWidth));
var delta = percentage * (this.MaxValue - this.MinValue);
value = this.MinValue + (float)delta;
}
if (gesture.State == UIGestureRecognizerState.Began)
{
UIView.Animate(0.35, 0, UIViewAnimationOptions.CurveEaseInOut,
() =>
{
this.SetValue(value, true);
},
null);
}
else
{
this.SetValue(value, animated: false);
}
}
}
如果您使用的是最近的稳定器 Xamarin.Forms Microsoft Documentation 有一个更简单的方法供您使用。基本上在 iOS 上为滑块设置一个 属性 特定命名空间。惊喜!
这是 Xaml 中的要点:
<ContentPage ...
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core">
<StackLayout ...>
<Slider ... ios:Slider.UpdateOnTap="true" />
...
</StackLayout>
或在后面的代码中:
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
...
//(in constructor or OnAppearing say)
var slider = new Xamarin.Forms.Slider();
slider.On<iOS>().SetUpdateOnTap(true);
希望对您有所帮助。
我使用滑块来制作星级评分功能。为此,我将滑块放在背景中以接收手指的位置。这在 android 上工作得很好,因为每当你触摸滑块时它都会改变它的值。 iOS 上的 UISlider 仅在您触摸其拇指时触发事件。
我觉得,可以这样做。
1.Add 点击手势到整个滑块控件
UILongPressGestureRecognizer uiTap = new UILongPressGestureRecognizer(Tapped);
uiTap.MinimumPressDuration = 0;
AddGestureRecognizer(uiTap);
2。从触摸读取坐标并将它们传递给控件。 但是我不知道该怎么做。
我的 xaml 看起来像这样。
<Grid>
<Slider/>
<Grid>
<!-- here is my stars -->
</Grid>
</Grid>
这里有很好的答案,代码在 objective-c:
好的,我能够将 swift 上的代码从 this answer 调整为 C# 代码,所以现在一切都按预期工作。
这是我的代码:
using MyProject.iOS.Renderers;
using CoreGraphics;
using Foundation;
using System;
using System.Linq;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Slider), typeof(ClickableSliderRenderer))]
namespace MyProject.iOS.Renderers
{
public class ClickableSliderRenderer : SliderRenderer
{
UILongPressGestureRecognizer uiTap;
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
base.OnElementChanged(e);
uiTap = new UILongPressGestureRecognizer(Tapped);
uiTap.MinimumPressDuration = 0;
AddGestureRecognizer(uiTap);
}
private void Tapped(object sender)
{
CGPoint point = uiTap.LocationInView(this);
nfloat thumbWidth = Control.Subviews.LastOrDefault().Bounds.Size.Width;
nfloat value;
if (point.X <= thumbWidth / 2.0)
value = Control.MinValue;
else if (point.X >= Control.Bounds.Size.Width - thumbWidth / 2)
value = Control.MaxValue;
else
{
var percentage = ((point.X - thumbWidth / 2) / (Control.Bounds.Size.Width - thumbWidth));
var delta = percentage * (Control.MaxValue - Control.MinValue);
value = Control.MinValue + delta;
}
if (uiTap.State == UIGestureRecognizerState.Began)
{
UIView.Animate(0.35, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animation:
() =>
{
Control.SetValue((float)value, true);
Control.SendActionForControlEvents(UIControlEvent.ValueChanged);
}, completion: null);
}
else if (uiTap.State == UIGestureRecognizerState.Changed)
{
Control.SetValue((float)value, false);
Control.SendActionForControlEvents(UIControlEvent.ValueChanged);
}
else
{
Control.SetValue((float)value, false);
}
Control.ThumbRectForBounds(Bounds, Bounds, (float)value);
}
}
}
我在 Xamarin iOS C#
中创建了一个类似的解决方案(改编自此answer)
[Register(nameof(UISliderCustom))]
[DesignTimeVisible(true)]
public class UISliderCustom : UISlider
{
public UISliderCustom(IntPtr handle) : base(handle) { }
public UISliderCustom()
{
// Called when created from code.
Initialize();
}
public override void AwakeFromNib()
{
// Called when loaded from xib or storyboard.
Initialize();
}
void Initialize()
{
// Common initialization code here.
var longPress = new UILongPressGestureRecognizer(tapAndSlide);
longPress.MinimumPressDuration = 0;
//longPress.CancelsTouchesInView = false;
this.AddGestureRecognizer(longPress);
this.UserInteractionEnabled = true;
}
private void tapAndSlide(UILongPressGestureRecognizer gesture)
{
System.Diagnostics.Debug.WriteLine($"{nameof(UISliderCustom)} RecognizerState {gesture.State}");
// need to propagate events down the chain
// I imagine iOS does something similar
// for whatever recogniser on the thumb control
// It's not enough to set CancelsTouchesInView because
// if clicking on the track away from the thumb control
// the thumb gesture recogniser won't pick it up anyway
switch (gesture.State)
{
case UIGestureRecognizerState.Cancelled:
this.SendActionForControlEvents(UIControlEvent.TouchCancel);
break;
case UIGestureRecognizerState.Began:
this.SendActionForControlEvents(UIControlEvent.TouchDown);
break;
case UIGestureRecognizerState.Changed:
this.SendActionForControlEvents(UIControlEvent.ValueChanged);
break;
case UIGestureRecognizerState.Ended:
this.SendActionForControlEvents(UIControlEvent.TouchUpInside);
break;
case UIGestureRecognizerState.Failed:
//?
break;
case UIGestureRecognizerState.Possible:
//?
break;
}
var pt = gesture.LocationInView(this);
var thumbWidth = CurrentThumbImage.Size.Width;
var value = 0f;
if (pt.X <= thumbWidth / 2)
{
value = this.MinValue;
}
else if (pt.X >= this.Bounds.Size.Width - thumbWidth / 2)
{
value = this.MaxValue;
}
else
{
var percentage = ((pt.X - thumbWidth / 2) / (this.Bounds.Size.Width - thumbWidth));
var delta = percentage * (this.MaxValue - this.MinValue);
value = this.MinValue + (float)delta;
}
if (gesture.State == UIGestureRecognizerState.Began)
{
UIView.Animate(0.35, 0, UIViewAnimationOptions.CurveEaseInOut,
() =>
{
this.SetValue(value, true);
},
null);
}
else
{
this.SetValue(value, animated: false);
}
}
}
如果您使用的是最近的稳定器 Xamarin.Forms Microsoft Documentation 有一个更简单的方法供您使用。基本上在 iOS 上为滑块设置一个 属性 特定命名空间。惊喜!
这是 Xaml 中的要点:
<ContentPage ...
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core">
<StackLayout ...>
<Slider ... ios:Slider.UpdateOnTap="true" />
...
</StackLayout>
或在后面的代码中:
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
...
//(in constructor or OnAppearing say)
var slider = new Xamarin.Forms.Slider();
slider.On<iOS>().SetUpdateOnTap(true);
希望对您有所帮助。