diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
index 1a68c4d732..9da9627a6f 100644
--- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
+++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
@@ -158,4 +158,4 @@
-
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
index 1a53217842..363330be86 100644
--- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
@@ -14,7 +14,8 @@
+ MinimumPrefixLength="1"
+ />
-
+
- View.DispatchKeyEvent(e) ? true : base.DispatchKeyEvent(e);
+ //public override bool DispatchKeyEvent(KeyEvent e) =>
+ // View.DispatchKeyEvent(e) ? true : base.DispatchKeyEvent(e);
}
}
diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs
index 8de3657283..f78f383041 100644
--- a/src/Android/Avalonia.Android/AvaloniaView.cs
+++ b/src/Android/Avalonia.Android/AvaloniaView.cs
@@ -2,6 +2,8 @@ using System;
using Android.Content;
using Android.Runtime;
using Android.Views;
+using Android.Views.Accessibility;
+using Android.Views.InputMethods;
using Android.Widget;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
@@ -23,6 +25,9 @@ namespace Avalonia.Android
AddView(_view.View);
_root = new EmbeddableControlRoot(_view);
_root.Prepare();
+
+ Focusable = true;
+ FocusableInTouchMode = true;
}
public object Content
@@ -89,5 +94,26 @@ namespace Avalonia.Android
public WindowState WindowState { get; set; }
public IDisposable ShowDialog() => null;
}
+
+ public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
+ {
+ base.OnInitializeAccessibilityNodeInfo(info);
+ }
+
+ public override AccessibilityNodeInfo CreateAccessibilityNodeInfo()
+ {
+ return base.CreateAccessibilityNodeInfo();
+ }
+
+ public override IInputConnection OnCreateInputConnection(EditorInfo outAttrs)
+ {
+ outAttrs.InputType = global::Android.Text.InputTypes.ClassNumber;
+ return base.OnCreateInputConnection(outAttrs);
+ }
+
+ public override bool OnCheckIsTextEditor()
+ {
+ return true;
+ }
}
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
index b115917622..aabf8160f8 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
@@ -32,7 +32,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
RowBytes = buffer.stride * (Format == PixelFormat.Rgb565 ? 2 : 4);
Address = buffer.bits;
- Dpi = scaling * new Vector(96, 96);
+ Dpi = new Vector(96, 96) * scaling;
}
public void Dispose()
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
index 16c5bdae3d..a1c2b2d9fb 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
@@ -2,23 +2,31 @@ using System;
using Android.Content;
using Android.Graphics;
using Android.OS;
+using Android.Runtime;
using Android.Util;
using Android.Views;
+using Android.Views.Accessibility;
+using Android.Views.InputMethods;
+using Avalonia.Android.Platform.Specific;
+using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.Android
{
- public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle
+ public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle, IAndroidSoftInput
{
bool _invalidateQueued;
+ private ISoftInputElement _softInputElement;
readonly object _lock = new object();
private readonly Handler _handler;
-
public InvalidationAwareSurfaceView(Context context) : base(context)
{
Holder.AddCallback(this);
_handler = new Handler(context.MainLooper);
+
+ Focusable = true;
+ FocusableInTouchMode = true;
}
public override void Invalidate()
@@ -83,5 +91,60 @@ namespace Avalonia.Android
}
protected abstract void Draw();
public string HandleDescriptor => "SurfaceView";
+
+ public override IInputConnection OnCreateInputConnection(EditorInfo outAttrs)
+ {
+ outAttrs.InputType = _softInputElement.InputType switch
+ {
+ InputType.Numeric => global::Android.Text.InputTypes.ClassNumber,
+ InputType.Phone => global::Android.Text.InputTypes.ClassPhone,
+ _ => global::Android.Text.InputTypes.Null
+ };
+
+ return base.OnCreateInputConnection(outAttrs);
+ }
+
+
+ public override bool CheckInputConnectionProxy(View view)
+ {
+ return base.CheckInputConnectionProxy(view);
+ }
+
+ public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
+ {
+ base.OnInitializeAccessibilityNodeInfo(info);
+ }
+
+ public override bool OnCheckIsTextEditor()
+ {
+ return true;
+ }
+
+ public void ShowSoftInput(ISoftInputElement softInputElement)
+ {
+ var input = Context.GetSystemService(Context.InputMethodService).JavaCast();
+ var previousSoftInput = _softInputElement;
+ _softInputElement = softInputElement;
+
+ if (_softInputElement.InputType == InputType.None)
+ HideSoftInput();
+ else
+ {
+ RequestFocus();
+
+ if (!ReferenceEquals(_softInputElement, previousSoftInput))
+ {
+ input.RestartInput(this);
+ }
+
+ input.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.NotAlways);
+ }
+ }
+
+ public void HideSoftInput()
+ {
+ var input = Context.GetSystemService(Context.InputMethodService).JavaCast();
+ input.HideSoftInputFromWindow(WindowToken, HideSoftInputFlags.None);
+ }
}
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index fe237a1719..219dd4cf67 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -19,7 +19,7 @@ using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform
{
- class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo
+ class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, IAndroidSoftInput
{
private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer;
@@ -43,18 +43,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
+
+ _keyboardHelper.ActivateAutoShowKeyboard();
}
- private bool _handleEvents;
public bool HandleEvents
{
- get { return _handleEvents; }
- set
- {
- _handleEvents = value;
- _keyboardHelper.HandleEvents = _handleEvents;
- }
+ get { return _keyboardHelper.HandleEvents; }
+ set { _keyboardHelper.HandleEvents = value; }
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@@ -213,5 +210,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
throw new NotImplementedException();
}
+
+ void IAndroidSoftInput.ShowSoftInput(ISoftInputElement softInputElement)
+ {
+ _view.ShowSoftInput(softInputElement);
+ }
+
+ void IAndroidSoftInput.HideSoftInput()
+ {
+ _view.HideSoftInput();
+ }
}
}
diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
index 426b221738..264c9f4cb5 100644
--- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
+++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
@@ -1,9 +1,11 @@
using System;
using System.ComponentModel;
+using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
+using Android.Widget;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
@@ -12,7 +14,7 @@ using Avalonia.Input.Raw;
namespace Avalonia.Android.Platform.Specific.Helpers
{
- internal class AndroidKeyboardEventsHelper : IDisposable where TView : TopLevelImpl, IAndroidView
+ internal class AndroidKeyboardEventsHelper : IDisposable where TView : TopLevelImpl, IAndroidView, IAndroidSoftInput
{
private TView _view;
private IInputElement _lastFocusedElement;
@@ -36,9 +38,20 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return DispatchKeyEventInternal(e, out callBase);
}
+ string? UnicodeTextInput(KeyEvent keyEvent)
+ {
+ return keyEvent.Action == KeyEventActions.Multiple
+ && keyEvent.RepeatCount == 0
+ && !string.IsNullOrEmpty(keyEvent?.Characters)
+ ? keyEvent.Characters
+ : null;
+ }
+
private bool? DispatchKeyEventInternal(KeyEvent e, out bool callBase)
{
- if (e.Action == KeyEventActions.Multiple)
+ var unicodeTextInput = UnicodeTextInput(e);
+
+ if (e.Action == KeyEventActions.Multiple && unicodeTextInput == null)
{
callBase = true;
return null;
@@ -53,13 +66,14 @@ namespace Avalonia.Android.Platform.Specific.Helpers
_view.Input(rawKeyEvent);
- if (e.Action == KeyEventActions.Down && e.UnicodeChar >= 32)
+ if ((e.Action == KeyEventActions.Down && e.UnicodeChar >= 32)
+ || unicodeTextInput != null)
{
var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime),
_view.InputRoot,
- Convert.ToChar(e.UnicodeChar).ToString()
+ unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString()
);
_view.Input(rawTextEvent);
}
@@ -88,41 +102,49 @@ namespace Avalonia.Android.Platform.Specific.Helpers
private bool NeedsKeyboard(IInputElement element)
{
//may be some other elements
- return element is TextBox;
+ return element is ISoftInputElement;
}
- private void TryShowHideKeyboard(IInputElement element, bool value)
+ private void TryShowHideKeyboard(ISoftInputElement element, bool value)
{
- var input = _view.View.Context.GetSystemService(Context.InputMethodService).JavaCast();
-
if (value)
{
- //show keyboard
- //may be in the future different keyboards support e.g. normal, only digits etc.
- //Android.Text.InputTypes
- input.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
+ _view.ShowSoftInput(element);
}
else
{
- //hide keyboard
- input.HideSoftInputFromWindow(_view.View.WindowToken, HideSoftInputFlags.None);
+ _view.HideSoftInput();
}
}
public void UpdateKeyboardState(IInputElement element)
{
- var focusedElement = element;
- bool oldValue = NeedsKeyboard(_lastFocusedElement);
- bool newValue = NeedsKeyboard(focusedElement);
+ var focusedElement = element as ISoftInputElement;
+ var lastElement = _lastFocusedElement as ISoftInputElement;
+
+ bool oldValue = lastElement?.InputType > InputType.None;
+ bool newValue = focusedElement?.InputType > InputType.None;
if (newValue != oldValue || newValue)
{
+ if (_lastFocusedElement != null)
+ _lastFocusedElement.PointerReleased -= RestoreSoftKeyboard;
+
TryShowHideKeyboard(focusedElement, newValue);
+
+ if (newValue && focusedElement != null)
+ element.PointerReleased += RestoreSoftKeyboard;
}
_lastFocusedElement = element;
}
+ private void RestoreSoftKeyboard(object sender, PointerReleasedEventArgs e)
+ {
+ if (_lastFocusedElement is ISoftInputElement softInputElement && softInputElement.InputType != InputType.None)
+ TryShowHideKeyboard(softInputElement, true);
+ }
+
public void ActivateAutoShowKeyboard()
{
var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged);
diff --git a/src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs b/src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs
index c72de8e197..db65a1335b 100644
--- a/src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs
+++ b/src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs
@@ -1,9 +1,19 @@
using Android.Views;
+using Avalonia.Input;
namespace Avalonia.Android.Platform.Specific
{
public interface IAndroidView
{
View View { get; }
+
+
+ }
+
+ public interface IAndroidSoftInput
+ {
+ void ShowSoftInput(ISoftInputElement softInputElement);
+
+ void HideSoftInput();
}
}
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 1d75f08a41..06729fa990 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -18,7 +18,7 @@ using Avalonia.Controls.Metadata;
namespace Avalonia.Controls
{
[PseudoClasses(":empty")]
- public class TextBox : TemplatedControl, UndoRedoHelper.IUndoRedoHost
+ public class TextBox : TemplatedControl, UndoRedoHelper.IUndoRedoHost, ISoftInputElement
{
public static KeyGesture CutGesture { get; } = AvaloniaLocator.Current
.GetService()?.Cut.FirstOrDefault();
@@ -130,6 +130,11 @@ namespace Avalonia.Controls
nameof(CanPaste),
o => o.CanPaste);
+ public static readonly DirectProperty InputTypeProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(InputType),
+ o => o.InputType);
+
struct UndoRedoState : IEquatable
{
public string Text { get; }
@@ -1243,5 +1248,7 @@ namespace Avalonia.Controls
ClearSelection();
}
}
+
+ public InputType InputType { get; set; } = InputType.Text;
}
}
diff --git a/src/Avalonia.Input/ISoftInputElement.cs b/src/Avalonia.Input/ISoftInputElement.cs
new file mode 100644
index 0000000000..ed5e4e59e3
--- /dev/null
+++ b/src/Avalonia.Input/ISoftInputElement.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Input
+{
+ public interface ISoftInputElement
+ {
+ InputType InputType { get; }
+ }
+}
diff --git a/src/Avalonia.Input/InputType.cs b/src/Avalonia.Input/InputType.cs
new file mode 100644
index 0000000000..893f9dcc39
--- /dev/null
+++ b/src/Avalonia.Input/InputType.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Input
+{
+ ///
+ /// Input type enumeration
+ ///
+ public enum InputType
+ {
+ ///
+ /// Do not use input
+ ///
+ None,
+
+ ///
+ /// User full text input
+ ///
+ Text,
+
+ ///
+ /// Use numeric text input
+ ///
+ Numeric,
+
+ ///
+ /// Use phone input
+ ///
+ Phone
+ }
+}