带秒的时间选择器 [英] Timepicker with seconds

查看:22
本文介绍了带秒的时间选择器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

这是我的渲染器代码:

公共类 TimePickerSecondsRenderer : TimePickerRenderer{protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e){base.OnElementChanged(e);var timePicker = (UIDatePicker)Control.InputView;timePicker.Locale = new NSLocale("CA");}}

我认为这是朝着正确方向迈出的一小步,但在为秒添加第三列以及每列的标签时,我真的不知所措.

我看了这个

唯一的缺点是标签hr"、min"和sec"不可用.都在他们自己的专栏里.因此,如果用户尝试与他们交互,则会产生反弹效果.我会尝试看看我以后是否可以改进它.欢迎提出建议!

当然还有代码(如果它对某人有用的话):

渲染器

[程序集:ExportRendererAttribute(typeof(MyTimePicker), typeof(MyTimePickerRenderer))]命名空间 MyProject.iOS.Renderers{公共类 MyTimePickerRenderer : PickerRenderer{内部静态 IDevice 设备;内部 const int ComponentCount = 6;私有常量 int _labelSize = 30;私人 MyTimePicker _myTimePicker;公共 MyTimePickerRenderer(){//这取决于 XForms(请参阅更新说明)Device = Resolver.Resolve();}protected override void OnElementChanged (ElementChangedEventArgs<Picker> e){base.OnElementChanged (e);如果(控制!= null){Control.BorderStyle = UITextBorderStyle.None;_myTimePicker = e.NewElement 作为 MyTimePicker;var customModelPickerView = 新 UIPickerView{模型 = 新的 MyTimePickerView(_myTimePicker)};SelectPickerValue(customModelPickerView, _myTimePicker);CreatePickerLabels(customModelPickerView);Control.InputView = customModelPickerView;}}私有无效 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);}私有无效 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 (e.PropertyName == "SelectedIndex"){var customModelPickerView = (UIPickerView)Control.InputView;SelectPickerValue(customModelPickerView, _myTimePicker);}}公共类 MyTimePickerView : UIPickerViewModel{私有只读 MyTimePicker _myTimePicker;公共 MyTimePickerView(MyTimePicker 选择器){_myTimePicker = 选择器;}公共覆盖 nint GetComponentCount(UIPickerView pickerView){返回新的 nint(MyTimePickerRenderer.ComponentCount);}公共覆盖 nint GetRowsInComponent(UIPickerView pickerView, nint 组件){如果(组件== 0){//小时返回新的 nint(24);}如果(组件 % 2 != 0){//奇数组件是小时、分钟和秒的标签返回新的 nint(1);}//分钟 &秒返回新的 nint(60);}公共覆盖字符串 GetTitle(UIPickerView pickerView, nint row, nint component){如果(组件== 0){返回 row.ToString();}否则如果(组件== 1){返回空;}否则如果(组件== 3){返回空;}否则如果(组件== 5){返回空;}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 = 时间;}公共覆盖 nfloat GetComponentWidth(UIPickerView pickerView, nint component){var screenWidth = MyTimePickerRenderer.Device.Display.Width;var 组件宽度 = 屏幕宽度/MyTimePickerRenderer.ComponentCount/MyTimePickerRenderer.Device.Display.Scale;返回新的 nfloat(componentWidth);}}}

自定义可绑定选择器类

public class MyTimePicker : Picker{公共静态只读 BindableProperty SelectedTimeProperty =BindableProperty.Create(p => p.SelectedTime, TimeSpan.MinValue, BindingMode.TwoWay,propertyChanged: OnSelectedTimePropertyPropertyChanged);公共 MyTimePicker(){//丑陋的黑客,因为 Xamarin Forms 的 Picker 在内部只使用一个组件//这是从 0:00:00 到 23:59:59 的所有可能时间跨度的列表for (int 小时 = 0; 小时 <24; 小时++){for (int 分钟 = 0; 分钟 < 60; 分钟 ++){for (int second = 0; second <60; second++){Items.Add(string.Format("{0:D2}:{1:D2}:{2:D2}", 小时, 分钟, 秒));}}}base.SelectedIndexChanged += (o, e) =>{if (base.SelectedIndex <0){SelectedTime = TimeSpan.MinValue;返回;}整数索引 = 0;foreach(项目中的变量项目){if (index == SelectedIndex){SelectedTime = TimeSpan.Parse(item);休息;}指数++;}};}公共时间跨度 SelectedTime{获取{返回(时间跨度)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 = 索引;}}

XAML 用法

其中 SelectedTime 是用于绑定的 ViewModel 属性.必须是 TimeSpan 类型.

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


更新

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

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

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

I am using Xamarin Forms and somewhere in my application, I need the user to be able to enter a time in the format HH:mm:ss. So, basically I need a control like this one:

By using a custom iOS render, I've been able to remove the AM/PM part from the TimePicker to achieve something like this:

Here's my renderer code:

public class TimePickerSecondsRenderer : TimePickerRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
    {
        base.OnElementChanged(e);

        var timePicker = (UIDatePicker)Control.InputView;
        timePicker.Locale = new NSLocale("CA");

    }
}

It's a small step in the right direction I suppose, but I'm really at loss when it comes to adding a third column for the seconds, as well as the labels for each column.

I took a look at this post but it doesn't really help me so far.

Did anyone managed to achieve that kind of control with their Xamarin Forms project? Would you mind sharing some pointers?

解决方案

I finally got a working implementation. Here's the result:

The only downside is that the labels "hr", "min", and "sec" are in their own column. As such, there is a bounce effect if the user tries to interact with them. I'll try to see if I can improve it later. Suggestions welcome!

And, of course, the code (if it can be of use to someone):

The renderer

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

Custom bindable picker 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 usage

<myControls:MyTimePicker SelectedTime="{Binding SelectedTime}" />

Where SelectedTime is your ViewModel property used for binding. Must be of type TimeSpan.

I'm sure it can be improved, so comment away if you have suggestions.


UPDATE

I've updated the renderer code.

  • The "hrs", "mins" and "secs" are now label and no longer columns of their own. That means that the user no longer can interact with them (no more bounce effect)
  • I've also fixed a bug where, if the picker value was set from the ViewModel, upon opening the keyboard the selection was still at 00:00:00
  • Better support for iPhone Plus resolution

Note that this code is dependent on XForms (https://github.com/XLabs/Xamarin-Forms-Labs) to get the device screen size, because that's the framework that I'm using for my project, but it can easily be modified.

这篇关于带秒的时间选择器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆