diff options
author | E.Z. Hart <hartez@gmail.com> | 2017-01-25 11:54:54 -0700 |
---|---|---|
committer | Rui Marinho <me@ruimarinho.net> | 2017-02-17 11:11:46 +0000 |
commit | 2ad9cb93f47f46fcb0584370ab8c297b20912718 (patch) | |
tree | 96868b61a3a9da1fa98ce4bb0d74e7d0c61e76c8 /Xamarin.Forms.Platform.Android/Renderers | |
parent | a1c7f9909a16d3253d9afd5cb2e1a839c6fb5a8c (diff) | |
download | xamarin-forms-2ad9cb93f47f46fcb0584370ab8c297b20912718.tar.gz xamarin-forms-2ad9cb93f47f46fcb0584370ab8c297b20912718.tar.bz2 xamarin-forms-2ad9cb93f47f46fcb0584370ab8c297b20912718.zip |
Add localized listener for Android numeric input
Diffstat (limited to 'Xamarin.Forms.Platform.Android/Renderers')
4 files changed, 247 insertions, 3 deletions
diff --git a/Xamarin.Forms.Platform.Android/Renderers/EditorRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/EditorRenderer.cs index c9b596bc..dba55ea8 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/EditorRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/EditorRenderer.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using Android.Content.Res; using Android.Text; +using Android.Text.Method; using Android.Util; using Android.Views; using Java.Lang; @@ -86,6 +87,14 @@ namespace Xamarin.Forms.Platform.Android base.OnElementPropertyChanged(sender, e); } + protected NumberKeyListener GetDigitsKeyListener(InputTypes inputTypes) + { + // Override this in a custom renderer to use a different NumberKeyListener + // or to filter out input types you don't want to allow + // (e.g., inputTypes &= ~InputTypes.NumberFlagSigned to disallow the sign) + return LocalizedDigitsKeyListener.Create(inputTypes); + } + internal override void OnNativeFocusChanged(bool hasFocus) { if (Element.IsFocused && !hasFocus) // Editor has requested an unfocus, fire completed event @@ -102,7 +111,14 @@ namespace Xamarin.Forms.Platform.Android { Editor model = Element; EditorEditText edit = Control; - edit.InputType = model.Keyboard.ToInputType() | InputTypes.TextFlagMultiLine; + var keyboard = model.Keyboard; + + edit.InputType = keyboard.ToInputType() | InputTypes.TextFlagMultiLine; + + if (keyboard == Keyboard.Numeric) + { + edit.KeyListener = GetDigitsKeyListener(edit.InputType); + } } void UpdateText() diff --git a/Xamarin.Forms.Platform.Android/Renderers/EntryRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/EntryRenderer.cs index c1c24a83..7d533708 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/EntryRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/EntryRenderer.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using Android.Content.Res; using Android.Text; +using Android.Text.Method; using Android.Util; using Android.Views; using Android.Views.InputMethods; @@ -116,6 +117,14 @@ namespace Xamarin.Forms.Platform.Android base.OnElementPropertyChanged(sender, e); } + protected virtual NumberKeyListener GetDigitsKeyListener(InputTypes inputTypes) + { + // Override this in a custom renderer to use a different NumberKeyListener + // or to filter out input types you don't want to allow + // (e.g., inputTypes &= ~InputTypes.NumberFlagSigned to disallow the sign) + return LocalizedDigitsKeyListener.Create(inputTypes); + } + void UpdateAlignment() { Control.Gravity = Element.HorizontalTextAlignment.ToHorizontalGravityFlags(); @@ -156,7 +165,15 @@ namespace Xamarin.Forms.Platform.Android void UpdateInputType() { Entry model = Element; - _textView.InputType = model.Keyboard.ToInputType(); + var keyboard = model.Keyboard; + + _textView.InputType = keyboard.ToInputType(); + + if (keyboard == Keyboard.Numeric) + { + _textView.KeyListener = GetDigitsKeyListener(_textView.InputType); + } + if (model.IsPassword && ((_textView.InputType & InputTypes.ClassText) == InputTypes.ClassText)) _textView.InputType = _textView.InputType | InputTypes.TextVariationPassword; if (model.IsPassword && ((_textView.InputType & InputTypes.ClassNumber) == InputTypes.ClassNumber)) diff --git a/Xamarin.Forms.Platform.Android/Renderers/KeyboardExtensions.cs b/Xamarin.Forms.Platform.Android/Renderers/KeyboardExtensions.cs index a2e60179..6941422d 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/KeyboardExtensions.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/KeyboardExtensions.cs @@ -20,7 +20,7 @@ namespace Xamarin.Forms.Platform.Android else if (self == Keyboard.Email) result = InputTypes.ClassText | InputTypes.TextVariationEmailAddress; else if (self == Keyboard.Numeric) - result = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal; + result = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned; else if (self == Keyboard.Telephone) result = InputTypes.ClassPhone; else if (self == Keyboard.Text) diff --git a/Xamarin.Forms.Platform.Android/Renderers/LocalizedDigitsKeyListener.cs b/Xamarin.Forms.Platform.Android/Renderers/LocalizedDigitsKeyListener.cs new file mode 100644 index 00000000..009cffbb --- /dev/null +++ b/Xamarin.Forms.Platform.Android/Renderers/LocalizedDigitsKeyListener.cs @@ -0,0 +1,211 @@ +using System.Collections.Generic; +using Android.Text; +using Android.Text.Method; +using Java.Lang; +using Java.Text; + +namespace Xamarin.Forms.Platform.Android +{ + internal class LocalizedDigitsKeyListener : NumberKeyListener + { + readonly char _decimalSeparator; + + // I'm not aware of a situation/locale where this would need to be something different, + // but we'll make it easy to localize the sign in the future just in case + const char SignCharacter = '-'; + + static Dictionary<char, LocalizedDigitsKeyListener> s_unsignedCache; + static Dictionary<char, LocalizedDigitsKeyListener> s_signedCache; + + static char GetDecimalSeparator() + { + var format = NumberFormat.Instance as DecimalFormat; + if (format == null) + { + return '.'; + } + + DecimalFormatSymbols sym = format.DecimalFormatSymbols; + return sym.DecimalSeparator; + } + + public static NumberKeyListener Create(InputTypes inputTypes) + { + if ((inputTypes & InputTypes.NumberFlagDecimal) == 0) + { + // If decimal isn't allowed, we can just use the Android version + return DigitsKeyListener.GetInstance(inputTypes.HasFlag(InputTypes.NumberFlagSigned), false); + } + + // Figure out what the decimal separator is for the current locale + char decimalSeparator = GetDecimalSeparator(); + + if (decimalSeparator == '.') + { + // If it's '.', then we can just use the default Android version + return DigitsKeyListener.GetInstance(inputTypes.HasFlag(InputTypes.NumberFlagSigned), true); + } + + // If decimals are enabled and the locale's decimal separator is not '.' + // (which is hard-coded in the Android DigitKeyListener), then use + // our custom one with a configurable decimal separator + return GetInstance(inputTypes, decimalSeparator); + } + + public static LocalizedDigitsKeyListener GetInstance(InputTypes inputTypes, char decimalSeparator) + { + if ((inputTypes & InputTypes.NumberFlagSigned) != 0) + { + return GetInstance(inputTypes, decimalSeparator, ref s_signedCache); + } + + return GetInstance(inputTypes, decimalSeparator, ref s_unsignedCache); + } + + static LocalizedDigitsKeyListener GetInstance(InputTypes inputTypes, char decimalSeparator, ref Dictionary<char, LocalizedDigitsKeyListener> cache) + { + if (cache == null) + { + cache = new Dictionary<char, LocalizedDigitsKeyListener>(1); + } + + if (!cache.ContainsKey(decimalSeparator)) + { + cache.Add(decimalSeparator, new LocalizedDigitsKeyListener(inputTypes, decimalSeparator)); + } + + return cache[decimalSeparator]; + } + + protected LocalizedDigitsKeyListener(InputTypes inputTypes, char decimalSeparator) + { + _decimalSeparator = decimalSeparator; + InputType = inputTypes; + } + + public override InputTypes InputType { get; } + + char[] _acceptedChars; + + protected override char[] GetAcceptedChars() + { + if ((InputType & InputTypes.NumberFlagSigned) == 0) + { + return _acceptedChars ?? + (_acceptedChars = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', _decimalSeparator }); + } + + return _acceptedChars ?? + (_acceptedChars = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', SignCharacter, _decimalSeparator }); + } + + static bool IsSignChar(char c) + { + return c == SignCharacter; + } + + bool IsDecimalPointChar(char c) + { + return c == _decimalSeparator; + } + + public override ICharSequence FilterFormatted(ICharSequence source, int start, int end, ISpanned dest, int dstart, + int dend) + { + // Borrowed heavily from the Android source + ICharSequence filterFormatted = base.FilterFormatted(source, start, end, dest, dstart, dend); + + if (filterFormatted != null) + { + source = filterFormatted; + start = 0; + end = filterFormatted.Length(); + } + + int sign = -1; + int dec = -1; + int dlen = dest.Length(); + + // Find out if the existing text has a sign or decimal point characters. + for (var i = 0; i < dstart; i++) + { + char c = dest.CharAt(i); + if (IsSignChar(c)) + { + sign = i; + } + else if (IsDecimalPointChar(c)) + { + dec = i; + } + } + + for (int i = dend; i < dlen; i++) + { + char c = dest.CharAt(i); + if (IsSignChar(c)) + { + return new String(""); // Nothing can be inserted in front of a sign character. + } + + if (IsDecimalPointChar(c)) + { + dec = i; + } + } + + // If it does, we must strip them out from the source. + // In addition, a sign character must be the very first character, + // and nothing can be inserted before an existing sign character. + // Go in reverse order so the offsets are stable. + SpannableStringBuilder stripped = null; + for (int i = end - 1; i >= start; i--) + { + char c = source.CharAt(i); + var strip = false; + + if (IsSignChar(c)) + { + if (i != start || dstart != 0) + { + strip = true; + } + else if (sign >= 0) + { + strip = true; + } + else + { + sign = i; + } + } + else if (IsDecimalPointChar(c)) + { + if (dec >= 0) + { + strip = true; + } + else + { + dec = i; + } + } + + if (strip) + { + if (end == start + 1) + { + return new String(""); // Only one character, and it was stripped. + } + if (stripped == null) + { + stripped = new SpannableStringBuilder(source, start, end); + } + stripped.Delete(i - start, i + 1 - start); + } + } + + return stripped ?? filterFormatted; + } + } +}
\ No newline at end of file |