c# - 带秒的时间选择器

标签 c# ios xamarin xamarin.ios xamarin.forms

我正在使用 Xamarin Forms,在我的应用程序的某处,我需要用户能够以 HH:mm:ss 格式输入时间。所以,基本上我需要一个像这样的控件:

Timepicker with seconds

通过使用自定义 iOS 渲染,我已经能够从 TimePicker 中删除 AM/PM 部分以实现如下目的:

enter image description here

这是我的渲染器代码:

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 项目实现这种控制?您介意分享一些建议吗?

最佳答案

我终于得到了一个有效的实现。结果如下:

enter image description here

唯一的缺点是标签“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);
        }
    }
}

自定义可绑定(bind)选择器类

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 是用于绑定(bind)的 ViewModel 属性。必须是 TimeSpan 类型。

我相信它可以改进,所以如果您有任何建议,请发表评论。


更新

我已经更新了渲染器代码。

  • “hrs”、“mins”和“secs”现在是标签,不再是它们自己的列。这意味着用户无法再与他们互动(不再有弹跳效果)
  • 我还修复了一个错误,如果选择器值是从 ViewModel 设置的,则在打开键盘时选择仍然在 00:00:00
  • 更好地支持 iPhone Plus 分辨率

请注意,此代码依赖于 XForms ( https://github.com/XLabs/Xamarin-Forms-Labs) 来获取设备屏幕尺寸,因为这是我用于我的项目的框架,但它可以很容易地修改。

关于c# - 带秒的时间选择器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35931470/

相关文章:

c# - 相对布局 (XAML) 中的布局项

c# - 无法在 ASP.NET Core 2.0 中对 .js 文件进行响应压缩

c# - 通过命令行构建和发布 C# .NET Web 应用程序

iphone - 使用 Test Flight App 对随机数进行 Beta 测试有什么危险?

objective-c - 有什么方法可以指定 NSMutableArray 的对象类吗?

c# - 在 Xamarin 中使用 TouchImageView

c# - 如何动态覆盖 Html.ActionLink 生成的 URL?

c# - 我在 WP7 的 bingmaps 中的图钉都是黑色的。为什么?

ios - 我如何知道 iOS 上的默认度量系统(英制或公制)?

Xamarin.Forms:如何将 NavigationPage XAML 类添加到另一个 XAML 类?