Timepicker avec secondes

J'utilise Xamarin Forms et quelque part dans mon application, j'ai besoin que l'user puisse entrer une heure au format HH: mm: ss. Donc, fondamentalement, j'ai besoin d'un contrôle comme celui-ci:

Timepicker avec secondes

En utilisant un rendu iOS personnalisé, j'ai pu supprimer la partie AM / PM du TimePicker pour get quelque chose comme ceci:

entrez la description de l'image ici

Voici mon code de rendu:

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

C'est un petit pas dans la bonne direction je suppose, mais je suis vraiment à la perte quand il s'agit d'append une troisième colonne pour les secondes, ainsi que les labels pour chaque colonne.

J'ai regardé ce post mais ça ne m'a pas vraiment aidé jusqu'ici.

Est-ce que quelqu'un a réussi à get ce genre de contrôle avec son projet Xamarin Forms? Pourriez-vous partager quelques pointeurs?

J'ai finalement eu une implémentation fonctionnelle. Voici le résultat:

entrez la description de l'image ici

Le seul inconvénient est que les labels "hr", "min" et "sec" sont dans leur propre colonne. En tant que tel, il y a un effet de rebond si l'user essaie d'interagir avec eux. Je vais essayer de voir si je peux l'améliorer plus tard. Suggestions bienvenues!

Et, bien sûr, le code (si cela peut être utile à quelqu'un):

Le moteur de rendu

 [assembly: ExportRendererAtsortingbute(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 ssortingng GetTitle(UIPickerView pickerView, nint row, nint component) { if (component == 0) { return row.ToSsortingng(); } else if (component == 1) { return null; } else if (component == 3) { return null; } else if (component == 5) { return null; } return row.ToSsortingng("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); } } } 

Classe de sélecteur liable personnalisée

 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(ssortingng.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.ToSsortingng()); var index = picker.Items.IndexOf(itemMatch); picker.SelectedIndex = index; } } 

Utilisation de XAML

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

Où SelectedTime est votre propriété ViewModel utilisée pour la binding. Doit être de type TimeSpan.

Je suis sûr que cela peut être amélioré, alors n'hésitez pas si vous avez des suggestions.

METTRE À JOUR

J'ai mis à jour le code du moteur de rendu.

  • Les "hrs", "mins" et "secs" sont maintenant des labels et non plus des colonnes qui leur sont propres. Cela signifie que l'user ne peut plus interagir avec eux (plus d'effet de rebond)
  • J'ai également corrigé un bug où, si la valeur du sélecteur était définie à partir de ViewModel, à l'ouverture du keyboard, la sélection était toujours à 00:00:00
  • Meilleur support pour la résolution iPhone Plus

Notez que ce code dépend de XForms ( https://github.com/XLabs/Xamarin-Forms-Labs ) pour get la taille de l'écran de l'appareil, car c'est le framework que j'utilise pour mon projet, mais il peut facilement être modifié.