summaryrefslogtreecommitdiff
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
parenta1c7f9909a16d3253d9afd5cb2e1a839c6fb5a8c (diff)
downloadxamarin-forms-2ad9cb93f47f46fcb0584370ab8c297b20912718.tar.gz
xamarin-forms-2ad9cb93f47f46fcb0584370ab8c297b20912718.tar.bz2
xamarin-forms-2ad9cb93f47f46fcb0584370ab8c297b20912718.zip
Add localized listener for Android numeric input
-rw-r--r--Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs30
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42000.cs65
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems1
-rw-r--r--Xamarin.Forms.Platform.Android/Cells/EntryCellRenderer.cs18
-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
-rw-r--r--Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj5
9 files changed, 362 insertions, 7 deletions
diff --git a/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs b/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs
index 86876a38..4c6c1a89 100644
--- a/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs
+++ b/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs
@@ -19,12 +19,18 @@ using AButton = Android.Widget.Button;
using AView = Android.Views.View;
using Android.OS;
using System.Reflection;
+using Android.Text;
+using Android.Text.Method;
using Xamarin.Forms.Controls.Issues;
[assembly: ExportRenderer(typeof(Bugzilla31395.CustomContentView), typeof(CustomContentRenderer))]
[assembly: ExportRenderer(typeof(NativeListView), typeof(NativeListViewRenderer))]
[assembly: ExportRenderer(typeof(NativeListView2), typeof(NativeAndroidListViewRenderer))]
[assembly: ExportRenderer(typeof(NativeCell), typeof(NativeAndroidCellRenderer))]
+
+[assembly: ExportRenderer(typeof(Bugzilla42000._42000NumericEntryNoDecimal), typeof(EntryRendererNoDecimal))]
+[assembly: ExportRenderer(typeof(Bugzilla42000._42000NumericEntryNoNegative), typeof(EntryRendererNoNegative))]
+
#if PRE_APPLICATION_CLASS
#elif FORMS_APPLICATION_ACTIVITY
#else
@@ -485,5 +491,29 @@ namespace Xamarin.Forms.ControlGallery.Android
base.OnElementChanged(e);
}
}
+
+ // Custom renderers for Bugzilla42000 demonstration purposes
+
+ public class EntryRendererNoNegative : EntryRenderer
+ {
+ protected override NumberKeyListener GetDigitsKeyListener(InputTypes inputTypes)
+ {
+ // Disable the NumberFlagSigned bit
+ inputTypes &= ~InputTypes.NumberFlagSigned;
+
+ return base.GetDigitsKeyListener(inputTypes);
+ }
+ }
+
+ public class EntryRendererNoDecimal : EntryRenderer
+ {
+ protected override NumberKeyListener GetDigitsKeyListener(InputTypes inputTypes)
+ {
+ // Disable the NumberFlagDecimal bit
+ inputTypes &= ~InputTypes.NumberFlagDecimal;
+
+ return base.GetDigitsKeyListener(inputTypes);
+ }
+ }
}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42000.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42000.cs
new file mode 100644
index 00000000..77436bcd
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla42000.cs
@@ -0,0 +1,65 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Bugzilla, 42000, "Unable to use comma (\", \") as decimal point", PlatformAffected.Android)]
+ public class Bugzilla42000 : ContentPage
+ {
+ public Bugzilla42000()
+ {
+ var instructions = new Label
+ {
+ Text =
+ @"Change your system language settings and verify that you can type the correct decimal separator into the Entry and Editor controls below.
+If your language is set to English (United States), you should be able to type '2.5', but not '2.5.3' or '2,5'.
+If your language is set to Deutsch (Deutschland), you should be able to type '2,5', but not '2,5,3' or '2.5'.
+"
+ };
+
+ var entrylabel = new Label { Text = "Entry:" };
+ var entry = new Entry { Keyboard = Keyboard.Numeric };
+
+ var editorlabel = new Label { Text = "Editor:" };
+ var editor = new Editor { Keyboard = Keyboard.Numeric };
+
+ var customRendererInstructions = new Label
+ {
+ Margin = new Thickness(0, 40, 0, 0),
+ Text = @"The two entries below demonstrate disabling decimal separators and negative numbers, respectively.
+In the first one, neither '.' nor ',' should be typeable.
+In the second, the '-' should not be typeable."
+ };
+
+ var entryNoDecimal = new _42000NumericEntryNoDecimal { Keyboard = Keyboard.Numeric };
+ var entryNoNegative = new _42000NumericEntryNoNegative { Keyboard = Keyboard.Numeric };
+
+ Content = new StackLayout
+ {
+ VerticalOptions = LayoutOptions.Center,
+ Children =
+ {
+ instructions,
+ entrylabel,
+ entry,
+ editorlabel,
+ editor,
+ customRendererInstructions,
+ entryNoDecimal,
+ entryNoNegative
+ }
+ };
+ }
+
+ [Preserve(AllMembers = true)]
+ public class _42000NumericEntryNoDecimal : Entry
+ {
+ }
+
+ [Preserve(AllMembers = true)]
+ public class _42000NumericEntryNoNegative : Entry
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
index e98c36c8..e5119636 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
@@ -132,6 +132,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla41424.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla41600.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla41619.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla42000.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42069.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42069_Page.xaml.cs">
<DependentUpon>Bugzilla42069_Page.xaml</DependentUpon>
diff --git a/Xamarin.Forms.Platform.Android/Cells/EntryCellRenderer.cs b/Xamarin.Forms.Platform.Android/Cells/EntryCellRenderer.cs
index e46422a0..bfe8777f 100644
--- a/Xamarin.Forms.Platform.Android/Cells/EntryCellRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Cells/EntryCellRenderer.cs
@@ -1,5 +1,7 @@
using System.ComponentModel;
using Android.Content;
+using Android.Text;
+using Android.Text.Method;
using Android.Views;
namespace Xamarin.Forms.Platform.Android
@@ -56,6 +58,14 @@ namespace Xamarin.Forms.Platform.Android
UpdateHeight();
}
+ 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);
+ }
+
void OnEditingCompleted()
{
var entryCell = (IEntryCellController)Cell;
@@ -88,7 +98,13 @@ namespace Xamarin.Forms.Platform.Android
void UpdateKeyboard()
{
var entryCell = (EntryCell)Cell;
- _view.EditText.InputType = entryCell.Keyboard.ToInputType();
+ var keyboard = entryCell.Keyboard;
+ _view.EditText.InputType = keyboard.ToInputType();
+
+ if (keyboard == Keyboard.Numeric)
+ {
+ _view.EditText.KeyListener = GetDigitsKeyListener(_view.EditText.InputType);
+ }
}
void UpdateLabel()
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
diff --git a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
index b622ecb4..89cf67e0 100644
--- a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
+++ b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
@@ -172,6 +172,7 @@
<Compile Include="Renderers\KeyboardExtensions.cs" />
<Compile Include="AppCompat\PickerRenderer.cs" />
<Compile Include="AppCompat\ViewRenderer.cs" />
+ <Compile Include="Renderers\LocalizedDigitsKeyListener.cs" />
<Compile Include="Renderers\MasterDetailContainer.cs" />
<Compile Include="Renderers\PageContainer.cs" />
<Compile Include="Renderers\ScrollViewContainer.cs" />
@@ -266,9 +267,7 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
- <ItemGroup>
- <Folder Include="Extensions\" />
- </ItemGroup>
+ <ItemGroup />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Import Project="..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">