Entry con navegación en Xamarin Forms

La mayoría de las veces, cuando desarrollo una aplicación Xamarin Forms en la que el usuario tiene que introducir datos, echo de menos poder indicar en el Entry cuál es el siguiente control. Con esto se conseguiría que el usuario pudiese ir pasando de un control a otro desde el teclado, sin necesidad de ir cambiando el foco manualmente. Dada esta necesidad, he decidido crear un proyecto con un control personalizado al que he llamado NavigableEntry, con el que se consigue vitaminar un Entry pudiendo definir cuál será el siguiente control. Para conseguirlo, lo único que he tenido que hacer es una clase que implemente un Entry y le dé la funcionalidad deseada.

public class NavigableEntryControl : Entry
{
    public static readonly BindableProperty NextViewProperty = BindableProperty.Create(nameof(NextView), typeof(View), typeof(Entry));
    public View NextView
    {
        get => (View)GetValue(NextViewProperty);
        set => SetValue(NextViewProperty, value);
    }

    protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        base.OnPropertyChanged(propertyName);

        this.Completed += (sender, e) =>
        {
            this.OnNext();
        };
    }

    public void OnNext()
    {
        NextView?.Focus();
    }
}


Como se aprecia en el código anterior, lo que se ha hecho es añadir un nuevo BindableProperty que permita definir cuál es el siguiente control, siendo este de tipo View, lo que nos permite especificar cualquier control de Xamarin Forms. Además, cuando se lance el evento Completed se pone el foco en el NextView, consiguiendo la navegación entre controles.

Teniendo el control definido, el siguiente paso es utilizarlo en alguna página, para ello he creado una pagina con tres Entry.

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:NavigableEntryTest" 
             x:Class="NavigableEntryTest.MainPage"
             xmlns:ctrl="clr-namespace:NavigableEntry;assembly=NavigableEntry">
    <StackLayout Padding="25,35">
            <ctrl:NavigableEntryControl x:Name="EntryOne" Placeholder="First Entry" HeightRequest="45"/>
            <ctrl:NavigableEntryControl x:Name="EntryTwo" Placeholder="Second Entry" HeightRequest="45" Keyboard="Numeric"/>
            <ctrl:NavigableEntryControl x:Name="EntryThree" Placeholder="Third Entry" HeightRequest="45" Keyboard="Numeric"/>
            <BoxView/>
            <Button Text="Save" VerticalOptions="End"/>
    </StackLayout>
</ContentPage>


 

Para definir la navegación, basta con indicar, para cada Entry cuál será el siguiente View al que se quiere navegar.

EntryOne.NextView = EntryTwo;
EntryTwo.NextView = EntryThree;


 

Con estos pasos, se podría creer que se ha terminado el desarrollo, pero falta un pequeño detalle. Y es que hay algún problema en cada plataforma.

  • En Android, cada vez que navegamos de un control al siguiente, el teclado desaparece y vuelve a aparecer, lo que crea un efecto incomodo y poco profesional.
  • En iOS, cuando se utiliza un teclado numérico, no tenemos ningún botón para navegar al siguiente control.

Para solucionar ambos problemas, se ha creado un efecto.

En el caso de Android, se valida si el control es un NavigableEntryControl y tiene definido un NextView, en cuyo caso, al pulsar el botón que valida el control, se llama al OnNext() consiguiendo navegación y eliminando ese efecto desagradable que hacía el teclado.

[assembly: ResolutionGroupName("Effects")]
[assembly: ExportEffect(typeof(NavigableEntryEffect), nameof(NavigableEntryEffect))]
namespace NavigableEntry_Android.Effects
{
    [Android.Runtime.Preserve(AllMembers = true)]
    public class NavigableEntryEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            // Check if the attached element is of the expected type and has the NextEntry
            // property set. if so, configure the keyboard to indicate there is another entry
            // in the form and the dismiss action to focus on the next entry
            if (base.Element is NavigableEntryControl xfControl && xfControl.NextView != null)
            {
                var entry = (Android.Widget.EditText)Control;

                entry.ImeOptions = Android.Views.InputMethods.ImeAction.Next;
                entry.EditorAction += (sender, args) =>
                {
                    xfControl.OnNext();
                };
            }
        }

        protected override void OnDetached()
        {
            // Intentionally empty
        }
    }
}


 

Para iOS, se validad si el control es un NavigableEntryControl y tiene definido un NextView, en cuyo caso, la tecla de retorno se cambia por UIReturnKeyType.Next. Asimismo, si el teclado es numérico, se muestra una toolbar en la que se añade el botón Next Done, en función de si se ha definido un NextView o no.

[assembly: ResolutionGroupName("Effects")]
[assembly: ExportEffect(typeof(NavigableEntryEffect), nameof(NavigableEntryEffect))]
namespace NavigableEntry_iOS.Effects
{
    [Foundation.Preserve(AllMembers = true)]
    public class NavigableEntryEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            var entry = (UITextField)Control;

            // Change return key to Don key
            entry.ReturnKeyType = UIReturnKeyType.Done;

            // Add the "done" button to the toolbar easily dismiss the keyboard
            // when it may not have a return key
            if (entry.KeyboardType == UIKeyboardType.DecimalPad ||
                entry.KeyboardType == UIKeyboardType.NumberPad)
            {
                entry.InputAccessoryView = BuildDismiss();
            }

            // Check if the attached element is of the expected type and has the NextEntry
            // property set. if so, configure the keyboard to indicate there is another entry
            // in the form and the dismiss action to focus on the next entry
            if (base.Element is NavigableEntryControl xfControl && xfControl.NextView != null)
            {
                entry.ReturnKeyType = UIReturnKeyType.Next;
            }
        }

        protected override void OnDetached()
        {
            // Intentionally empty
        }

        private UIToolbar BuildDismiss()
        {
            var toolbar = new UIToolbar(new CGRect(0.0f, 0.0f, Control.Frame.Size.Width, 44.0f));

            // Set default state of buttonAction/appearance
            UIBarButtonItem buttonAction = new UIBarButtonItem(UIBarButtonSystemItem.Done, delegate { Control.ResignFirstResponder(); });

            // If we have a next element, swap out the default state for "Next"
            if (base.Element is NavigableEntryControl xfControl && xfControl.NextView != null)
            {
                buttonAction = new UIBarButtonItem("Next", UIBarButtonItemStyle.Plain, delegate
                {
                    xfControl.OnNext();
                });
            }

            toolbar.Items = new[]
            {
                new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace),
                buttonAction,
            };

            return toolbar;
        }
    }
}


El último paso sería asociar estos efectos a nuestros tres NavigableEntryControl

var navigableEntryEffect = "Effects.NavigableEntryEffect";
EntryOne.Effects.Add(Effect.Resolve(navigableEntryEffect));
EntryTwo.Effects.Add(Effect.Resolve(navigableEntryEffect));
EntryThree.Effects.Add(Effect.Resolve(navigableEntryEffect));


 

Con todo lo anterior, ya tendríamos un Entry que nos permite definir cual será la siguiente View al que se navegará, aumentando la productividad y la experiencia de uso de los usuario de nuestra aplicación.

Como es habitual, he creado un proyecto en GitHub para ofrecer más detalles de la implementación que he realizado. Espero que os sea de ayuda

https://github.com/jorgediegocrespo/XamarinNavigableEntry

 

Bibliografía

Building a “Next Entry” Effect for iOS and Android in Xamarin.Forms

https://github.com/muak/AiForms.Effects

Anuncio publicitario

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.