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 o 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