Change return to be next/done key in Xamarin Forms Shared Project

被刻印的时光 ゝ 提交于 2019-11-28 06:20:29

A custom EntryRenderer can handle changing the keyboard return key description.

  • iOS : UITextField has a ReturnKeyType property that you can set to a preassigned list (see UIReturnType enum).

  • Android : EntryEditText has a ImeOptions property that controls what the "Action" button on the keyboard does and a SetImeActionLabel method that you can use to set any text string for it.

Usage Example of the custom Entry/EntryRenderer:

new EntryExt {
    Text = "Next Key",
    ReturnKeyType = ReturnKeyTypes.Next
},
new EntryExt {
    Text = "Done Key",
    ReturnKeyType = ReturnKeyTypes.Done
}

A Xamarin.Forms custom Entry class:

namespace YourNameSpaceHere
{
    public class EntryExt : Entry
    {
        public const string ReturnKeyPropertyName = "ReturnKeyType";

        public EntryExt() { }

        public static readonly BindableProperty ReturnKeyTypeProperty = BindableProperty.Create(
            propertyName: ReturnKeyPropertyName,
            returnType: typeof(ReturnKeyTypes),
            declaringType: typeof(EntryExt),
            defaultValue: ReturnKeyTypes.Done);

        public ReturnKeyTypes ReturnKeyType
        {
            get { return (ReturnKeyTypes)GetValue(ReturnKeyTypeProperty); }
            set { SetValue(ReturnKeyTypeProperty, value); }
        }
    }

    // Not all of these are support on Android, consult EntryEditText.ImeOptions
    public enum ReturnKeyTypes : int
    {
        Default,
        Go,
        Google,
        Join,
        Next,
        Route,
        Search,
        Send,
        Yahoo,
        Done,
        EmergencyCall,
        Continue
    }
}

iOS custom EntryRenderer:

[assembly: ExportRenderer(typeof(Entry), typeof(EntryExtRenderer_iOS))]
namespace KeyboardDone.iOS
{
    public class EntryExtRenderer_iOS : EntryRenderer
    {
        public EntryExtRenderer_iOS() { }

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
            if ((Control != null) && (e.NewElement != null))
                Control.ReturnKeyType = (e.NewElement as EntryExt).ReturnKeyType.GetValueFromDescription();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == EntryExt.ReturnKeyPropertyName)
            {
                D.WriteLine($"{(sender as EntryExt).ReturnKeyType.ToString()}");
                Control.ReturnKeyType = (sender as EntryExt).ReturnKeyType.GetValueFromDescription();
            }
        }
    }

    public static class EnumExtensions
    {
        public static UIReturnKeyType GetValueFromDescription(this ReturnKeyTypes value)
        {
            var type = typeof(UIReturnKeyType);
            if (!type.IsEnum) throw new InvalidOperationException();
            foreach (var field in type.GetFields())
            {
                var attribute = Attribute.GetCustomAttribute(field,
                    typeof(DescriptionAttribute)) as DescriptionAttribute;
                if (attribute != null)
                {
                    if (attribute.Description == value.ToString())
                        return (UIReturnKeyType)field.GetValue(null);
                }
                else
                {
                    if (field.Name == value.ToString())
                        return (UIReturnKeyType)field.GetValue(null);
                }
            }
            throw new NotSupportedException($"Not supported on iOS: {value}");
        }
    }
}

Android custom EntryRenderer:

[assembly: ExportRenderer(typeof(Entry), typeof(EntryExtRenderer_Droid))]
namespace KeyboardDone.Droid
{
    public class EntryExtRenderer_Droid : EntryRenderer
    {
        public EntryExtRenderer_Droid() { }

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
            if ((Control != null) && (e.NewElement != null))
            {
                var entryExt = (e.NewElement as EntryExt);
                Control.ImeOptions = entryExt.ReturnKeyType.GetValueFromDescription();
                // This is hackie ;-) / A Android-only bindable property should be added to the EntryExt class 
                Control.SetImeActionLabel(entryExt.ReturnKeyType.ToString(), Control.ImeOptions);
            }
        }
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == EntryExt.ReturnKeyPropertyName)
            {
                var entryExt = (sender as EntryExt);
                Control.ImeOptions = entryExt.ReturnKeyType.GetValueFromDescription();
                // This is hackie ;-) / A Android-only bindable property should be added to the EntryExt class 
                Control.SetImeActionLabel(entryExt.ReturnKeyType.ToString(), Control.ImeOptions);
            }
        }

    }
    public static class EnumExtensions
    {
        public static ImeAction GetValueFromDescription(this ReturnKeyTypes value)
        {
            var type = typeof(ImeAction);
            if (!type.IsEnum) throw new InvalidOperationException();
            foreach (var field in type.GetFields())
            {
                var attribute = Attribute.GetCustomAttribute(field,
                    typeof(DescriptionAttribute)) as DescriptionAttribute;
                if (attribute != null)
                {
                    if (attribute.Description == value.ToString())
                        return (ImeAction)field.GetValue(null);
                }
                else
                {
                    if (field.Name == value.ToString())
                        return (ImeAction)field.GetValue(null);
                }
            }
            throw new NotSupportedException($"Not supported on Android: {value}");
        }
    }
}

Here is an alternative approach, but similar to the solution of SushiHangover. It includes UWP support:

ReturnType.cs in PCL

public enum ReturnType
{
    Go,
    Next,
    Done,
    Send,
    Search
}

BaseEntry.cs in PCL

public class BaseEntry : Entry
{
    // Need to overwrite default handler because we cant Invoke otherwise
    public new event EventHandler Completed;

    public static readonly BindableProperty ReturnTypeProperty = BindableProperty.Create(
        nameof(ReturnType),
        typeof(ReturnType),
        typeof(BaseEntry),
        ReturnType.Done, 
        BindingMode.OneWay
    );

    public ReturnType ReturnType
    {
        get { return (ReturnType)GetValue(ReturnTypeProperty); }
        set { SetValue(ReturnTypeProperty, value); }
    }

    public void InvokeCompleted()
    {
        if (this.Completed != null)
            this.Completed.Invoke(this, null);
    }
}

BaseEntryRenderer.cs for Android

public class BaseEntryRenderer : EntryRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
    {
        base.OnElementChanged(e);

        BaseEntry entry = (BaseEntry)this.Element;

        if(this.Control != null)
        {
            if(entry != null)
            {
                SetReturnType(entry);

                // Editor Action is called when the return button is pressed
                Control.EditorAction += (object sender, TextView.EditorActionEventArgs args) =>
                {
                    if (entry.ReturnType != ReturnType.Next)
                        entry.Unfocus();

                    // Call all the methods attached to base_entry event handler Completed
                    entry.InvokeCompleted();
                };
            }
        }
    }

    private void SetReturnType(BaseEntry entry)
    {
        ReturnType type = entry.ReturnType;

        switch (type)
        {
            case ReturnType.Go:
                Control.ImeOptions = ImeAction.Go;
                Control.SetImeActionLabel("Go", ImeAction.Go);
                break;
            case ReturnType.Next:
                Control.ImeOptions = ImeAction.Next;
                Control.SetImeActionLabel("Next", ImeAction.Next);
                break;
            case ReturnType.Send:
                Control.ImeOptions = ImeAction.Send;
                Control.SetImeActionLabel("Send", ImeAction.Send);
                break;
            case ReturnType.Search:
                Control.ImeOptions = ImeAction.Search;
                Control.SetImeActionLabel("Search", ImeAction.Search);
                break;
            default:
                Control.ImeOptions = ImeAction.Done;
                Control.SetImeActionLabel("Done", ImeAction.Done);
                break;
        }
    }
}

BaseEntryRenderer.cs for iOS

public class BaseEntryRenderer : EntryRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
    {
        base.OnElementChanged(e);

        BaseEntry entry = (BaseEntry)this.Element;

        if (this.Control != null)
        {
            if(entry != null)
            {
                SetReturnType(entry);

                Control.ShouldReturn += (UITextField tf) =>
                {
                    entry.InvokeCompleted();
                    return true;
                };
            }
        }
    }

    private void SetReturnType(BaseEntry entry)
    {
        ReturnType type = entry.ReturnType;

        switch (type)
        {
            case ReturnType.Go:
                Control.ReturnKeyType = UIReturnKeyType.Go;
                break;
            case ReturnType.Next:
                Control.ReturnKeyType = UIReturnKeyType.Next;
                break;
            case ReturnType.Send:
                Control.ReturnKeyType = UIReturnKeyType.Send;
                break;
            case ReturnType.Search:
                Control.ReturnKeyType = UIReturnKeyType.Search;
                break;
            case ReturnType.Done:
                Control.ReturnKeyType = UIReturnKeyType.Done;
                break;
            default:
                Control.ReturnKeyType = UIReturnKeyType.Default;
                break;
        }
    }
}

BaseEntryRenderer.cs for UWP

public class BaseEntryRenderer : EntryRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
    {
        base.OnElementChanged(e);

        BaseEntry entry = (BaseEntry)this.Element;

        if(this.Control != null)
        {
            if(entry != null)
            {
                this.Control.KeyDown += (object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs eventArgs) =>
                {
                    if (eventArgs.Key == Windows.System.VirtualKey.Enter)
                    {
                        entry.InvokeCompleted();
                        // Make sure to set the Handled to true, otherwise the RoutedEvent might fire twice
                        eventArgs.Handled = true;
                    }
                };
            }
        }
    }
}

On UWP it seems not be possible to change the return key to next/done/... You need to add ExportRenderer attributes yourself for all custom renderers.

Usage

XAML file

<renderer:BaseEntry x:Name="username" Text="Username" ReturnType="Next" />
<renderer:BaseEntry x:Name="password" Text ="Password" IsPassword="true" ReturnType="Done" />

Code behind file:

this.username.Completed += (object sender, EventArgs e) => this.password.Focus();

Based on this source.

The latest Xamarin Forms package adds the ReturnType attribute for Entry elements. It will also execute a command when the Done button is clicked. The IMEAction types for Done, Next, Search, Go and Send are all supported now.

Yes latest Xamarin Forms allowed directly ReturnType as property, just need to add ReturnType in Xaml

 <Entry x:Name="myEntry" ReturnType="Done"/>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!