时间戳与秒

Mat*_*ieu 2 c# xamarin.ios ios xamarin xamarin-forms

我在我的应用程序中使用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");

    }
}
Run Code Online (Sandbox Code Playgroud)

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

我看了看这篇文章,但到目前为止它并没有真正帮助我.

有没有人设法通过他们的Xamarin Forms项目实现这种控制?你介意分享一些指示吗?

Mat*_*ieu 6

我终于得到了一个有效的实现.这是结果:

在此输入图像描述

唯一的缺点是标签"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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

自定义可绑定选择器类

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

XAML用法

<myControls:MyTimePicker SelectedTime="{Binding SelectedTime}" />
Run Code Online (Sandbox Code Playgroud)

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

我确信它可以改进,所以如果你有建议请评论.


UPDATE

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

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

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

  • 谢谢Mathieu!我修改了你的例子并创建了一个要点https://gist.github.com/yuv4ik/c7137c4ea89ededa99dfee51bfb1de4e (2认同)
  • 由于项目数量过多,存在性能问题。在最新版本(第一条评论中提供的链接)中,我只使用了一个虚拟项目。由于 SelectedTime 包含选定的值,我只更新 OnSelectedTimePropertyPropertyChanged 上的虚拟项目值。 (2认同)