summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.Android/Renderers
diff options
context:
space:
mode:
authorE.Z. Hart <hartez@gmail.com>2017-01-25 11:54:54 -0700
committerRui Marinho <me@ruimarinho.net>2017-02-17 11:11:46 +0000
commit2ad9cb93f47f46fcb0584370ab8c297b20912718 (patch)
tree96868b61a3a9da1fa98ce4bb0d74e7d0c61e76c8 /Xamarin.Forms.Platform.Android/Renderers
parenta1c7f9909a16d3253d9afd5cb2e1a839c6fb5a8c (diff)
downloadxamarin-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')
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/EditorRenderer.cs18
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/EntryRenderer.cs19
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/KeyboardExtensions.cs2
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/LocalizedDigitsKeyListener.cs211
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