带秒的时间选择器
Timepicker with seconds
我正在使用 Xamarin Forms,在我的应用程序的某个地方,我需要用户能够以 HH:mm:ss 格式输入时间。所以,基本上我需要这样一个控件:
通过使用自定义 iOS 渲染器,我已经能够从 TimePicker 中删除 AM/PM 部分以实现如下效果:
这是我的渲染器代码:
public class TimePickerSecondsRenderer : TimePickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
{
base.OnElementChanged(e);
var timePicker = (UIDatePicker)Control.InputView;
timePicker.Locale = new NSLocale("CA");
}
}
我想这是朝着正确方向迈出的一小步,但在为秒数添加第三列以及每列的标签时,我真的不知所措。
我看过这个post,但到目前为止它并没有真正帮助我。
有没有人设法通过他们的 Xamarin Forms 项目实现这种控制?您介意分享一些建议吗?
我终于得到了一个有效的实现。结果如下:
唯一的缺点是标签“hr”、“min”和“sec”位于它们自己的列中。因此,如果用户试图与他们互动,就会产生反弹效果。以后我试试看能不能改进
欢迎提出建议!
当然还有代码(如果对某人有用的话):
渲染器
[assembly: ExportRendererAttribute(typeof(MyTimePicker), typeof(MyTimePickerRenderer))]
namespace MyProject.iOS.Renderers
{
public class MyTimePickerRenderer : PickerRenderer
{
internal static IDevice Device;
internal const int ComponentCount = 6;
private const int _labelSize = 30;
private MyTimePicker _myTimePicker;
public MyTimePickerRenderer()
{
// This is dependent on XForms (see Update note)
Device = Resolver.Resolve<IDevice>();
}
protected override void OnElementChanged (ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged (e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.None;
_myTimePicker = e.NewElement as MyTimePicker;
var customModelPickerView = new UIPickerView
{
Model = new MyTimePickerView(_myTimePicker)
};
SelectPickerValue(customModelPickerView, _myTimePicker);
CreatePickerLabels(customModelPickerView);
Control.InputView = customModelPickerView;
}
}
private void SelectPickerValue(UIPickerView customModelPickerView, MyTimePicker myTimePicker)
{
customModelPickerView.Select(new nint(myTimePicker.SelectedTime.Hours), 0, false);
customModelPickerView.Select(new nint(myTimePicker.SelectedTime.Minutes), 2, false);
customModelPickerView.Select(new nint(myTimePicker.SelectedTime.Seconds), 4, false);
}
private void CreatePickerLabels(UIPickerView customModelPickerView)
{
nfloat verticalPosition = (customModelPickerView.Frame.Size.Height / 2) - (_labelSize / 2);
nfloat componentWidth = new nfloat(Device.Display.Width / ComponentCount / Device.Display.Scale);
var hoursLabel = new UILabel(new CGRect(componentWidth, verticalPosition, _labelSize, _labelSize));
hoursLabel.Text = "hrs";
var minutesLabel = new UILabel(new CGRect((componentWidth * 3) + (componentWidth / 2), verticalPosition, _labelSize, _labelSize));
minutesLabel.Text = "mins";
var secondsLabel = new UILabel(new CGRect((componentWidth * 5) + (componentWidth / 2), verticalPosition, _labelSize, _labelSize));
secondsLabel.Text = "secs";
customModelPickerView.AddSubview(hoursLabel);
customModelPickerView.AddSubview(minutesLabel);
customModelPickerView.AddSubview(secondsLabel);
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null)
{
return;
}
if (e.PropertyName == "SelectedIndex")
{
var customModelPickerView = (UIPickerView)Control.InputView;
SelectPickerValue(customModelPickerView, _myTimePicker);
}
}
public class MyTimePickerView : UIPickerViewModel
{
private readonly MyTimePicker _myTimePicker;
public MyTimePickerView(MyTimePicker picker)
{
_myTimePicker = picker;
}
public override nint GetComponentCount(UIPickerView pickerView)
{
return new nint(MyTimePickerRenderer.ComponentCount);
}
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
if (component == 0)
{
// Hours
return new nint(24);
}
if (component % 2 != 0)
{
// Odd components are labels for hrs, mins and secs
return new nint(1);
}
// Minutes & seconds
return new nint(60);
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
{
return row.ToString();
}
else if (component == 1)
{
return null;
}
else if (component == 3)
{
return null;
}
else if (component == 5)
{
return null;
}
return row.ToString("00");
}
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
var selectedHours = pickerView.SelectedRowInComponent(0);
var selectedMinutes = pickerView.SelectedRowInComponent(2);
var selectedSeconds = pickerView.SelectedRowInComponent(4);
var time = new TimeSpan((int)selectedHours, (int)selectedMinutes, (int)selectedSeconds);
_myTimePicker.SelectedTime = time;
}
public override nfloat GetComponentWidth(UIPickerView pickerView, nint component)
{
var screenWidth = MyTimePickerRenderer.Device.Display.Width;
var componentWidth = screenWidth /
MyTimePickerRenderer.ComponentCount /
MyTimePickerRenderer.Device.Display.Scale;
return new nfloat(componentWidth);
}
}
}
自定义可绑定选择器class
public class MyTimePicker : Picker
{
public static readonly BindableProperty SelectedTimeProperty =
BindableProperty.Create<MyTimePicker, TimeSpan>(p => p.SelectedTime, TimeSpan.MinValue, BindingMode.TwoWay,propertyChanged: OnSelectedTimePropertyPropertyChanged);
public MyTimePicker()
{
// Ugly hack since Xamarin Forms' Picker uses only one component internally
// This is a list of all possible timespan from 0:00:00 to 23:59:59
for (int hour = 0; hour < 24; hour++)
{
for (int minute = 0; minute < 60; minute ++)
{
for (int second = 0; second < 60; second++)
{
Items.Add(string.Format("{0:D2}:{1:D2}:{2:D2}", hour, minute, second));
}
}
}
base.SelectedIndexChanged += (o, e) =>
{
if (base.SelectedIndex < 0)
{
SelectedTime = TimeSpan.MinValue;
return;
}
int index = 0;
foreach (var item in Items)
{
if (index == SelectedIndex)
{
SelectedTime = TimeSpan.Parse(item);
break;
}
index++;
}
};
}
public TimeSpan SelectedTime
{
get { return (TimeSpan)GetValue(SelectedTimeProperty); }
set { SetValue(SelectedTimeProperty, value); }
}
private static void OnSelectedTimePropertyPropertyChanged(BindableObject bindable, TimeSpan value, TimeSpan newValue)
{
var picker = (MyTimePicker)bindable;
var itemMatch = picker.Items.FirstOrDefault(x => x == newValue.ToString());
var index = picker.Items.IndexOf(itemMatch);
picker.SelectedIndex = index;
}
}
XAML 用法
<myControls:MyTimePicker SelectedTime="{Binding SelectedTime}" />
其中 SelectedTime 是您用于绑定的 ViewModel 属性。必须是 TimeSpan 类型。
我相信它可以改进,所以如果您有任何建议,请发表评论。
更新
我更新了渲染器代码。
- “hrs”、“mins”和“secs”现在是标签,不再是它们自己的列。这意味着用户无法再与他们互动(不再有弹跳效果)
- 我还修复了一个错误,如果选择器值是从 ViewModel 设置的,则在打开键盘时选择仍然在 00:00:00
- 更好地支持 iPhone Plus 分辨率
请注意,此代码依赖于 XForms (https://github.com/XLabs/Xamarin-Forms-Labs) 来获取设备屏幕尺寸,因为这是我用于我的项目的框架,但它可以很容易地修改。
我正在使用 Xamarin Forms,在我的应用程序的某个地方,我需要用户能够以 HH:mm:ss 格式输入时间。所以,基本上我需要这样一个控件:
通过使用自定义 iOS 渲染器,我已经能够从 TimePicker 中删除 AM/PM 部分以实现如下效果:
这是我的渲染器代码:
public class TimePickerSecondsRenderer : TimePickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
{
base.OnElementChanged(e);
var timePicker = (UIDatePicker)Control.InputView;
timePicker.Locale = new NSLocale("CA");
}
}
我想这是朝着正确方向迈出的一小步,但在为秒数添加第三列以及每列的标签时,我真的不知所措。
我看过这个post,但到目前为止它并没有真正帮助我。
有没有人设法通过他们的 Xamarin Forms 项目实现这种控制?您介意分享一些建议吗?
我终于得到了一个有效的实现。结果如下:
唯一的缺点是标签“hr”、“min”和“sec”位于它们自己的列中。因此,如果用户试图与他们互动,就会产生反弹效果。以后我试试看能不能改进
欢迎提出建议!
当然还有代码(如果对某人有用的话):
渲染器
[assembly: ExportRendererAttribute(typeof(MyTimePicker), typeof(MyTimePickerRenderer))]
namespace MyProject.iOS.Renderers
{
public class MyTimePickerRenderer : PickerRenderer
{
internal static IDevice Device;
internal const int ComponentCount = 6;
private const int _labelSize = 30;
private MyTimePicker _myTimePicker;
public MyTimePickerRenderer()
{
// This is dependent on XForms (see Update note)
Device = Resolver.Resolve<IDevice>();
}
protected override void OnElementChanged (ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged (e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.None;
_myTimePicker = e.NewElement as MyTimePicker;
var customModelPickerView = new UIPickerView
{
Model = new MyTimePickerView(_myTimePicker)
};
SelectPickerValue(customModelPickerView, _myTimePicker);
CreatePickerLabels(customModelPickerView);
Control.InputView = customModelPickerView;
}
}
private void SelectPickerValue(UIPickerView customModelPickerView, MyTimePicker myTimePicker)
{
customModelPickerView.Select(new nint(myTimePicker.SelectedTime.Hours), 0, false);
customModelPickerView.Select(new nint(myTimePicker.SelectedTime.Minutes), 2, false);
customModelPickerView.Select(new nint(myTimePicker.SelectedTime.Seconds), 4, false);
}
private void CreatePickerLabels(UIPickerView customModelPickerView)
{
nfloat verticalPosition = (customModelPickerView.Frame.Size.Height / 2) - (_labelSize / 2);
nfloat componentWidth = new nfloat(Device.Display.Width / ComponentCount / Device.Display.Scale);
var hoursLabel = new UILabel(new CGRect(componentWidth, verticalPosition, _labelSize, _labelSize));
hoursLabel.Text = "hrs";
var minutesLabel = new UILabel(new CGRect((componentWidth * 3) + (componentWidth / 2), verticalPosition, _labelSize, _labelSize));
minutesLabel.Text = "mins";
var secondsLabel = new UILabel(new CGRect((componentWidth * 5) + (componentWidth / 2), verticalPosition, _labelSize, _labelSize));
secondsLabel.Text = "secs";
customModelPickerView.AddSubview(hoursLabel);
customModelPickerView.AddSubview(minutesLabel);
customModelPickerView.AddSubview(secondsLabel);
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null)
{
return;
}
if (e.PropertyName == "SelectedIndex")
{
var customModelPickerView = (UIPickerView)Control.InputView;
SelectPickerValue(customModelPickerView, _myTimePicker);
}
}
public class MyTimePickerView : UIPickerViewModel
{
private readonly MyTimePicker _myTimePicker;
public MyTimePickerView(MyTimePicker picker)
{
_myTimePicker = picker;
}
public override nint GetComponentCount(UIPickerView pickerView)
{
return new nint(MyTimePickerRenderer.ComponentCount);
}
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
if (component == 0)
{
// Hours
return new nint(24);
}
if (component % 2 != 0)
{
// Odd components are labels for hrs, mins and secs
return new nint(1);
}
// Minutes & seconds
return new nint(60);
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
{
return row.ToString();
}
else if (component == 1)
{
return null;
}
else if (component == 3)
{
return null;
}
else if (component == 5)
{
return null;
}
return row.ToString("00");
}
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
var selectedHours = pickerView.SelectedRowInComponent(0);
var selectedMinutes = pickerView.SelectedRowInComponent(2);
var selectedSeconds = pickerView.SelectedRowInComponent(4);
var time = new TimeSpan((int)selectedHours, (int)selectedMinutes, (int)selectedSeconds);
_myTimePicker.SelectedTime = time;
}
public override nfloat GetComponentWidth(UIPickerView pickerView, nint component)
{
var screenWidth = MyTimePickerRenderer.Device.Display.Width;
var componentWidth = screenWidth /
MyTimePickerRenderer.ComponentCount /
MyTimePickerRenderer.Device.Display.Scale;
return new nfloat(componentWidth);
}
}
}
自定义可绑定选择器class
public class MyTimePicker : Picker
{
public static readonly BindableProperty SelectedTimeProperty =
BindableProperty.Create<MyTimePicker, TimeSpan>(p => p.SelectedTime, TimeSpan.MinValue, BindingMode.TwoWay,propertyChanged: OnSelectedTimePropertyPropertyChanged);
public MyTimePicker()
{
// Ugly hack since Xamarin Forms' Picker uses only one component internally
// This is a list of all possible timespan from 0:00:00 to 23:59:59
for (int hour = 0; hour < 24; hour++)
{
for (int minute = 0; minute < 60; minute ++)
{
for (int second = 0; second < 60; second++)
{
Items.Add(string.Format("{0:D2}:{1:D2}:{2:D2}", hour, minute, second));
}
}
}
base.SelectedIndexChanged += (o, e) =>
{
if (base.SelectedIndex < 0)
{
SelectedTime = TimeSpan.MinValue;
return;
}
int index = 0;
foreach (var item in Items)
{
if (index == SelectedIndex)
{
SelectedTime = TimeSpan.Parse(item);
break;
}
index++;
}
};
}
public TimeSpan SelectedTime
{
get { return (TimeSpan)GetValue(SelectedTimeProperty); }
set { SetValue(SelectedTimeProperty, value); }
}
private static void OnSelectedTimePropertyPropertyChanged(BindableObject bindable, TimeSpan value, TimeSpan newValue)
{
var picker = (MyTimePicker)bindable;
var itemMatch = picker.Items.FirstOrDefault(x => x == newValue.ToString());
var index = picker.Items.IndexOf(itemMatch);
picker.SelectedIndex = index;
}
}
XAML 用法
<myControls:MyTimePicker SelectedTime="{Binding SelectedTime}" />
其中 SelectedTime 是您用于绑定的 ViewModel 属性。必须是 TimeSpan 类型。
我相信它可以改进,所以如果您有任何建议,请发表评论。
更新
我更新了渲染器代码。
- “hrs”、“mins”和“secs”现在是标签,不再是它们自己的列。这意味着用户无法再与他们互动(不再有弹跳效果)
- 我还修复了一个错误,如果选择器值是从 ViewModel 设置的,则在打开键盘时选择仍然在 00:00:00
- 更好地支持 iPhone Plus 分辨率
请注意,此代码依赖于 XForms (https://github.com/XLabs/Xamarin-Forms-Labs) 来获取设备屏幕尺寸,因为这是我用于我的项目的框架,但它可以很容易地修改。