Forum Xamarin.Forms

Timepicker 24hour and custom interval on Android

I can't seem to get this right. There are some old post on this subject but non of them offer a working solution. This is what I'm trying to get done:

  • Timepicker in 24hour format instead of am/pm
  • Custom interval, for example 15 minute or 20 minute interval in the picker
  • The custom picker must be able to show when Focus() is set in code

works easily in iOS but can't get the Android customrenderer to work :(

Anyone any ideas?

Posts

  • JohannesHerterJohannesHerter USMember ✭✭
    edited September 2016

    @ToineHoltmann

    Custom interval, for example 15 minute or 20 minute interval in the picker

    I just asked google for this problem. Seems like you need to extend the default TimePickerDialog. See this question on stackoverflow.

    The custom picker must be able to show when Focus() is set in code

    Since Xamarin calls a private method inside the TimePickerRender when the entry is clicked, you could try to override the OnElementPropertyChanged method and check for the focus property. You can then call Control.CallOnClick (); which should have the same effect as clicking on the entry field.

    Timepicker in 24hour format instead of am/pm

    This will be some kind of a problem. Although you can pass a boolean to show a TimePicker with 24 hour format in its constructor, the TimePickerDialog is created inside a private method and you cannot override it with your own implementation. I ended up with copying the TimePickerRender from Xamarins source and implementing some modifications (especially setting the 24 hour format and supporting nullable values).

    If you end up with copying the TimePickerRender from Xamarin, the Focus() problem can be resolved with just calling this.OnClick() (this is the private method called when the control is clicked) instead of Control.CallOnClick ();

  • ToineHoltmannToineHoltmann NLMember ✭✭

    @JohannesHerter

    Thanks for your response! I also thought about copying the TimePickerRenderer so I can modify it to my needs. But I have no clue how this is done? Isn't it part of the Xamarin Forms framework? Does it mean I have to make my own build of the library or is there some way to just use your own implementation for just the TimePickerRenderer?

    I'm curious about your implementation because it also allows nullable values. Do you want to share your TimePickerRenderer?

  • JohannesHerterJohannesHerter USMember ✭✭

    @ToineHoltmann
    You don't have to make your own build of the library. You create your own TimePickerRenderer which extends ViewRenderer<NullableTimePicker, EditText> (as the original TimePickerRenderer does). To start, you can just copy the source code of the original TimePickerRenderer. Be sure to use your own namespace and include the tag to export the Renderer e.g. [assembly: ExportRenderer(typeof(TimePicker), typeof(CustomTimePickerRenderer))]. There might be some errors with internal references and ambiguous references, but these shouldn't be hard to fix.

    I'm afraid I cannot share the exact code of our TimePickerRenderer, we got some pretty strict company policies over here. The TimePickerRenderer only checks for Element.NullableTime before setting Control.Text. The main part to support nullable values is done in our extension of Xamarin.Forms.TimePicker.

    If you need a nullable TimePicker take a look at XLabs on Github. I think they include nullable Date- and TimePickers. There also should be some shared implementations in the forums.

  • ToineHoltmannToineHoltmann NLMember ✭✭

    @JohannesHerter
    Thank you so much for your help. I got it all fixed!

  • ASHISHKUMAR.8068ASHISHKUMAR.8068 USMember ✭✭

    @TonieHoltmann Can you share how did you solved the issue ?

  • ToineHoltmannToineHoltmann NLMember ✭✭

    Hope this helps..

    `using Android.App;
    using System;
    using Android.Runtime;
    using Android.Widget;
    using Android.Views;
    using Android.Content;
    using Android.OS;
    using System.Collections.Generic;

    namespace App.Droid
    {
    public class TimePickerDialogIntervals : TimePickerDialog
    {
    private int _interval = 1;

        public TimePickerDialogIntervals(Context context, EventHandler<TimeSetEventArgs> callBack, int hourOfDay, int minute, bool is24HourView, int interval)
            : base(context, ThemeHoloLight, (sender, e) =>
            {
                callBack(sender, new TimeSetEventArgs(e.HourOfDay, e.Minute * interval));
            }, hourOfDay, minute / interval, is24HourView)
        {
            _interval = interval;
            FixSpinner(context, hourOfDay, minute, is24HourView);
        }
    
        protected TimePickerDialogIntervals(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
        {
        }
    
        public override void SetView(Android.Views.View view)
        {
            base.SetView(view);
        }
    
        void SetupMinutePicker(Android.Views.View view)
        {
            var numberPicker = FindMinuteNumberPicker(view as ViewGroup);
            if (numberPicker != null)
            {
                int i = _interval;
                List<string> values = new List<string>();
                values.Add("00");
                while (i < 60)
                {
                    if (i < 10)
                        values.Add("0" + i);
                    else
                        values.Add(i.ToString());
                    i += _interval;
                }
    
                numberPicker.MinValue = 0;
                numberPicker.MaxValue = values.Count - 1;
    
                numberPicker.SetDisplayedValues(values.ToArray());
            }
        }
    
        protected override void OnCreate(Android.OS.Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            GetButton((int)DialogButtonType.Negative).Visibility = Android.Views.ViewStates.Gone;
            this.SetCanceledOnTouchOutside(false);
    
        }
    
        private NumberPicker FindMinuteNumberPicker(ViewGroup viewGroup)
        {
            for (var i = 0; i < viewGroup.ChildCount; i++)
            {
                var child = viewGroup.GetChildAt(i);
                var numberPicker = child as NumberPicker;
                if (numberPicker != null)
                {
                    if (numberPicker.MaxValue == 59)
                    {
                        return numberPicker;
                    }
                }
    
                var childViewGroup = child as ViewGroup;
                if (childViewGroup != null)
                {
                    var childResult = FindMinuteNumberPicker(childViewGroup);
                    if (childResult != null)
                        return childResult;
                }
            }
    
            return null;
        }
    
        private void FixSpinner(Context context, int hourOfDay, int minute, bool is24HourView)
        {
            try
            {
                // Get the theme's android:timePickerMode
                int MODE_SPINNER = 1;
                var styleableClass = Java.Lang.Class.ForName("com.android.internal.R$styleable");
                var timePickerStyleableField = styleableClass.GetField("TimePicker");
                int[] timePickerStyleable = (int[])timePickerStyleableField.Get(null);
                var a = context.ObtainStyledAttributes(null, timePickerStyleable, Android.Resource.Attribute.TimePickerStyle, 0);
                var timePickerModeStyleableField = styleableClass.GetField("TimePicker_timePickerMode");
                int timePickerModeStyleable = timePickerModeStyleableField.GetInt(null);
                int mode = a.GetInt(timePickerModeStyleable, MODE_SPINNER);
                a.Recycle();
    
                Android.Widget.TimePicker timePicker = (Android.Widget.TimePicker)findField(Java.Lang.Class.FromType(typeof(TimePickerDialog)), Java.Lang.Class.FromType(typeof(Android.Widget.TimePicker)), "mTimePicker").Get(this);
                var delegateClass = Java.Lang.Class.ForName("android.widget.TimePicker$TimePickerDelegate");
                var delegateField = findField(Java.Lang.Class.FromType(typeof(Android.Widget.TimePicker)), delegateClass, "mDelegate");
                var delegatee = delegateField.Get(timePicker);
                Java.Lang.Class spinnerDelegateClass;
                if (Build.VERSION.SdkInt != BuildVersionCodes.Lollipop)
                {
                    spinnerDelegateClass = Java.Lang.Class.ForName("android.widget.TimePickerSpinnerDelegate");
                }
                else
                {
                    // TimePickerSpinnerDelegate was initially misnamed TimePickerClockDelegate in API 21!
                    spinnerDelegateClass = Java.Lang.Class.ForName("android.widget.TimePickerClockDelegate");
                }
    
                // In 7.0 Nougat for some reason the timePickerMode is ignored and the delegate is TimePickerClockDelegate
                if (delegatee.Class != spinnerDelegateClass)
                {
                    delegateField.Set(timePicker, null); // throw out the TimePickerClockDelegate!
                    timePicker.RemoveAllViews(); // remove the TimePickerClockDelegate views
                    var spinnerDelegateConstructor = spinnerDelegateClass.GetConstructors()[0];
                    spinnerDelegateConstructor.Accessible = true;
                    // Instantiate a TimePickerSpinnerDelegate
                    delegatee = spinnerDelegateConstructor.NewInstance(timePicker, context, null, Android.Resource.Attribute.TimePickerStyle, 0);
                    delegateField.Set(timePicker, delegatee); // set the TimePicker.mDelegate to the spinner delegate
                                                              // Set up the TimePicker again, with the TimePickerSpinnerDelegate
                    timePicker.SetIs24HourView(Java.Lang.Boolean.ValueOf(is24HourView));
                    timePicker.Hour = hourOfDay;
                    timePicker.Minute = minute;
                    timePicker.SetOnTimeChangedListener(this);
                }
                // set interval
                SetupMinutePicker(timePicker);
            }
            catch (Exception e)
            {
                throw new Java.Lang.RuntimeException(e.ToString());
            }
        }
    
        private static Java.Lang.Reflect.Field findField(Java.Lang.Class objectClass, Java.Lang.Class fieldClass, String expectedName)
        {
            try
            {
                var field = objectClass.GetDeclaredField(expectedName);
                field.Accessible = true;
                return field;
            }
            catch (Java.Lang.NoSuchFieldException e) { } // ignore
                                                         // search for it if it wasn't found under the expected ivar name
            foreach (var searchField in objectClass.GetDeclaredFields())
            {
                if (Java.Lang.Class.FromType(searchField.GetType()) == fieldClass)
                {
                    searchField.Accessible = true;
                    return searchField;
                }
            }
            return null;
        }
    }
    

    }`

    `using App.CustomRenderer;
    using Xamarin.Forms;
    using App.Droid;
    using Xamarin.Forms.Platform.Android;
    using Android.App;
    using System;
    using Android.Widget;
    using Android.Content;

    [assembly: ExportRenderer(typeof(MyTimePicker), typeof(MyTimePickerRenderer))]
    namespace App.Droid
    {
    public class MyTimePickerRenderer : TimePickerRenderer
    {
    public MyTimePickerRenderer()
    {
    }

        public MyTimePickerRenderer(Context context) : base()
        {
        }
    
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.TimePicker> e)
        {
            base.OnElementChanged(e);
            MyTimePicker myPicker = (MyTimePicker)this.Element;
    
            TimePickerDialogIntervals timePickerDlg = new TimePickerDialogIntervals(this.Context, new EventHandler<TimePickerDialog.TimeSetEventArgs>(UpdateDuration),
                Element.Time.Hours, Element.Time.Minutes, true, myPicker.Interval);
    
    
    
            var control = new EditText(this.Context);
    
            control.Focusable = false;
            control.FocusableInTouchMode = false;
            control.Clickable = false;
            control.Click += (sender, ea) =>
            {
                if (myPicker.NullableTime == null)
                {
                    timePickerDlg.UpdateTime(DateTime.Now.TimeOfDay.Hours + 1, 0);
                }
                timePickerDlg.Show();
            };
    
            control.SetTextSize(Android.Util.ComplexUnitType.Sp, myPicker.FontSize);
            control.TextAlignment = Android.Views.TextAlignment.ViewEnd;
            control.SetBackgroundColor(global::Android.Graphics.Color.White);
    
            if (Element.Format == "\\Kie\\zen >")
            {
                control.Text = "Kiezen >";
                control.SetTextColor(new Android.Graphics.Color(204, 204, 204));
            }
            else
            {
                control.Text = Element.Time.Hours.ToString("00") + ":" + Element.Time.Minutes.ToString("00");
            }            
    
            SetNativeControl(control);
        }
    
    
    
        void UpdateDuration(object sender, Android.App.TimePickerDialog.TimeSetEventArgs e)
        {
            Element.Time = new TimeSpan(e.HourOfDay, e.Minute, 0);
            if (Element.Format == "\\Kie\\zen >")
            {
                Control.Text = "Kiezen >";
                Control.SetTextColor(new Android.Graphics.Color(204,204,204));
            }
            else
            {
                Control.Text = Element.Time.Hours.ToString("00") + ":" + Element.Time.Minutes.ToString("00");
                Control.TextAlignment = Android.Views.TextAlignment.ViewEnd;
            }
        }
    }
    

    }`

  • MalcolmLallyMalcolmLally USMember

    Thanks a lot!

Sign In or Register to comment.