Browse Source

Support android InputType

pull/5735/head
ili 5 years ago
parent
commit
e6b13b66c5
  1. 2
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  2. 3
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  3. 2
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  4. 6
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  5. 26
      src/Android/Avalonia.Android/AvaloniaView.cs
  6. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  7. 67
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  8. 23
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  9. 56
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  10. 10
      src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs
  11. 9
      src/Avalonia.Controls/TextBox.cs
  12. 11
      src/Avalonia.Input/ISoftInputElement.cs
  13. 32
      src/Avalonia.Input/InputType.cs

2
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -158,4 +158,4 @@
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\build\AndroidWorkarounds.props" />
<Import Project="..\..\build\LegacyProject.targets" />
</Project>
</Project>

3
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@ -14,7 +14,8 @@
<TextBlock Text="MinimumPrefixLength: 1"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
MinimumPrefixLength="1"/>
MinimumPrefixLength="1"
/>
<TextBlock Text="MinimumPrefixLength: 3"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"

2
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -13,7 +13,7 @@
<StackPanel Orientation="Vertical" Spacing="8">
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200" />
<TextBox Width="200" Watermark="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" Watermark="Watermark" />
<TextBox Width="200" Watermark="Watermark" InputType="Numeric" />
<TextBox Width="200"
Watermark="Floating Watermark"
UseFloatingWatermark="True"

6
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -15,7 +15,7 @@ namespace Avalonia.Android
if (_content != null)
View.Content = _content;
SetContentView(View);
TakeKeyEvents(true);
//TakeKeyEvents(true);
base.OnCreate(savedInstanceState);
}
@ -33,7 +33,7 @@ namespace Avalonia.Android
}
}
public override bool DispatchKeyEvent(KeyEvent e) =>
View.DispatchKeyEvent(e) ? true : base.DispatchKeyEvent(e);
//public override bool DispatchKeyEvent(KeyEvent e) =>
// View.DispatchKeyEvent(e) ? true : base.DispatchKeyEvent(e);
}
}

26
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;
}
}
}

2
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()

67
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<InputMethodManager>();
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<InputMethodManager>();
input.HideSoftInputFromWindow(WindowToken, HideSoftInputFlags.None);
}
}
}

23
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();
}
}
}

56
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<TView> : IDisposable where TView : TopLevelImpl, IAndroidView
internal class AndroidKeyboardEventsHelper<TView> : 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<InputMethodManager>();
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);

10
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();
}
}

9
src/Avalonia.Controls/TextBox.cs

@ -18,7 +18,7 @@ using Avalonia.Controls.Metadata;
namespace Avalonia.Controls
{
[PseudoClasses(":empty")]
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost, ISoftInputElement
{
public static KeyGesture CutGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Cut.FirstOrDefault();
@ -130,6 +130,11 @@ namespace Avalonia.Controls
nameof(CanPaste),
o => o.CanPaste);
public static readonly DirectProperty<TextBox, InputType> InputTypeProperty =
AvaloniaProperty.RegisterDirect<TextBox, InputType>(
nameof(InputType),
o => o.InputType);
struct UndoRedoState : IEquatable<UndoRedoState>
{
public string Text { get; }
@ -1243,5 +1248,7 @@ namespace Avalonia.Controls
ClearSelection();
}
}
public InputType InputType { get; set; } = InputType.Text;
}
}

11
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; }
}
}

32
src/Avalonia.Input/InputType.cs

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Input
{
/// <summary>
/// Input type enumeration
/// </summary>
public enum InputType
{
/// <summary>
/// Do not use input
/// </summary>
None,
/// <summary>
/// User full text input
/// </summary>
Text,
/// <summary>
/// Use numeric text input
/// </summary>
Numeric,
/// <summary>
/// Use phone input
/// </summary>
Phone
}
}
Loading…
Cancel
Save