зеркало из https://github.com/DeGsoft/maui-linux.git
Entry ClearButtonVisibility Handler (#564)
* Basic implementation for Standard and iOS handler. * Property comment. * Sample Entry controls added to sample project. * PortHandler attributes added. * Implemented Android handler with OnTouch and OnFocus listeners. * Removed redundant check for clear button visibility on touch for Android handler. * EntryStub implementation. * Device tests for iOS and Android. * Merge from main Co-authored-by: E.Z. Hart <hartez@gmail.com>
This commit is contained in:
Родитель
613dbc0072
Коммит
14fbdce843
|
@ -550,6 +550,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
|
|||
}
|
||||
|
||||
// Entry clear button management
|
||||
[PortHandler("Focus management part might need to be reworked after IsFocused implementation.")]
|
||||
public abstract partial class EntryRendererBase<TControl>
|
||||
{
|
||||
Drawable _clearBtn;
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace Microsoft.Maui.Controls.Compatibility
|
|||
{
|
||||
typeof(Button),
|
||||
typeof(ContentPage),
|
||||
typeof(DatePicker),
|
||||
typeof(DatePicker),
|
||||
typeof(Editor),
|
||||
typeof(Entry),
|
||||
typeof(Label),
|
||||
|
|
|
@ -562,6 +562,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
|
|||
Control.UserInteractionEnabled = !Element.IsReadOnly;
|
||||
}
|
||||
|
||||
[PortHandler]
|
||||
void UpdateClearButtonVisibility()
|
||||
{
|
||||
Control.ClearButtonMode = Element.ClearButtonVisibility == ClearButtonVisibility.WhileEditing ? UITextFieldViewMode.WhileEditing : UITextFieldViewMode.Never;
|
||||
|
|
|
@ -50,6 +50,12 @@ namespace Maui.Controls.Sample.Pages
|
|||
verticalStack.Add(new Label { Text = loremIpsum, MaxLines = 2, LineBreakMode = LineBreakMode.TailTruncation });
|
||||
verticalStack.Add(new Label { Text = "This should have five times the line height!", LineHeight = 5 });
|
||||
|
||||
var visibleClearButtonEntry = new Entry() { ClearButtonVisibility = ClearButtonVisibility.WhileEditing, Placeholder = "This Entry will show clear button if has input." };
|
||||
var hiddenClearButtonEntry = new Entry() { ClearButtonVisibility = ClearButtonVisibility.Never, Placeholder = "This Entry will not..." };
|
||||
|
||||
verticalStack.Add(visibleClearButtonEntry);
|
||||
verticalStack.Add(hiddenClearButtonEntry);
|
||||
|
||||
verticalStack.Add(new Editor { Placeholder = "This is an editor placeholder." } );
|
||||
var paddingButton = new Button
|
||||
{
|
||||
|
|
|
@ -19,5 +19,10 @@
|
|||
/// Gets an enumeration value that controls the appearance of the return button.
|
||||
/// </summary>
|
||||
ReturnType ReturnType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumeration value that shows/hides clear button on the Entry.
|
||||
/// </summary>
|
||||
ClearButtonVisibility ClearButtonVisibility { get; }
|
||||
}
|
||||
}
|
|
@ -1,37 +1,60 @@
|
|||
using System;
|
||||
using Android.Content.Res;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.Text;
|
||||
using Android.Views;
|
||||
using AndroidX.AppCompat.Widget;
|
||||
using AndroidX.Core.Content;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using static Android.Views.View;
|
||||
|
||||
namespace Microsoft.Maui.Handlers
|
||||
{
|
||||
public partial class EntryHandler : AbstractViewHandler<IEntry, AppCompatEditText>
|
||||
{
|
||||
TextWatcher Watcher { get; } = new TextWatcher();
|
||||
EntryTouchListener TouchListener { get; } = new EntryTouchListener();
|
||||
EntryFocusChangeListener FocusChangeListener { get; } = new EntryFocusChangeListener();
|
||||
|
||||
static ColorStateList? DefaultTextColors { get; set; }
|
||||
static Drawable? ClearButtonDrawable { get; set; }
|
||||
|
||||
protected override AppCompatEditText CreateNativeView()
|
||||
{
|
||||
return new AppCompatEditText(Context);
|
||||
}
|
||||
|
||||
// Returns the default 'X' char drawable in the AppCompatEditText.
|
||||
protected virtual Drawable GetClearButtonDrawable()
|
||||
=> ContextCompat.GetDrawable(Context, Resource.Drawable.abc_ic_clear_material);
|
||||
|
||||
protected override void ConnectHandler(AppCompatEditText nativeView)
|
||||
{
|
||||
Watcher.Handler = this;
|
||||
TouchListener.Handler = this;
|
||||
FocusChangeListener.Handler = this;
|
||||
|
||||
nativeView.OnFocusChangeListener = FocusChangeListener;
|
||||
nativeView.AddTextChangedListener(Watcher);
|
||||
nativeView.SetOnTouchListener(TouchListener);
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(AppCompatEditText nativeView)
|
||||
{
|
||||
nativeView.RemoveTextChangedListener(Watcher);
|
||||
nativeView.SetOnTouchListener(null);
|
||||
nativeView.OnFocusChangeListener = null;
|
||||
|
||||
FocusChangeListener.Handler = null;
|
||||
Watcher.Handler = null;
|
||||
TouchListener.Handler = null;
|
||||
}
|
||||
|
||||
protected override void SetupDefaults(AppCompatEditText nativeView)
|
||||
{
|
||||
base.SetupDefaults(nativeView);
|
||||
|
||||
ClearButtonDrawable = GetClearButtonDrawable();
|
||||
DefaultTextColors = nativeView.TextColors;
|
||||
}
|
||||
|
||||
|
@ -94,6 +117,30 @@ namespace Microsoft.Maui.Handlers
|
|||
handler.TypedNativeView?.UpdateCharacterSpacing(entry);
|
||||
}
|
||||
|
||||
public static void MapClearButtonVisibility(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
handler.TypedNativeView?.UpdateClearButtonVisibility(entry, ClearButtonDrawable);
|
||||
}
|
||||
|
||||
void OnFocusedChange(bool hasFocus)
|
||||
{
|
||||
if (TypedNativeView == null || VirtualView == null)
|
||||
return;
|
||||
|
||||
// This will eliminate additional native property setting if not required.
|
||||
if (VirtualView.ClearButtonVisibility == ClearButtonVisibility.WhileEditing)
|
||||
TypedNativeView?.UpdateClearButtonVisibility(VirtualView, ClearButtonDrawable);
|
||||
}
|
||||
|
||||
bool OnTouch(MotionEvent? motionEvent)
|
||||
{
|
||||
if (TypedNativeView == null || VirtualView == null)
|
||||
return false;
|
||||
|
||||
// Check whether the touched position inbounds with clear button.
|
||||
return HandleClearButtonTouched(motionEvent);
|
||||
}
|
||||
|
||||
void OnTextChanged(string? text)
|
||||
{
|
||||
if (VirtualView == null || TypedNativeView == null)
|
||||
|
@ -105,6 +152,47 @@ namespace Microsoft.Maui.Handlers
|
|||
var nativeText = text ?? string.Empty;
|
||||
if (mauiText != nativeText)
|
||||
VirtualView.Text = nativeText;
|
||||
|
||||
// Text changed should trigger clear button visibility.
|
||||
TypedNativeView.UpdateClearButtonVisibility(VirtualView, ClearButtonDrawable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the touched position on the EditText is inbounds with clear button and clears if so.
|
||||
/// This will return True to handle OnTouch to prevent re-activating keyboard after clearing the text.
|
||||
/// </summary>
|
||||
/// <returns>True if clear button is clicked and Text is cleared. False if not.</returns>
|
||||
bool HandleClearButtonTouched(MotionEvent? motionEvent)
|
||||
{
|
||||
if (motionEvent == null || TypedNativeView == null || VirtualView == null)
|
||||
return false;
|
||||
|
||||
var rBounds = ClearButtonDrawable?.Bounds;
|
||||
|
||||
if (rBounds != null)
|
||||
{
|
||||
var x = motionEvent.GetX();
|
||||
var y = motionEvent.GetY();
|
||||
|
||||
if (motionEvent.Action == MotionEventActions.Up
|
||||
&& ((x >= (TypedNativeView.Right - rBounds.Width())
|
||||
&& x <= (TypedNativeView.Right - TypedNativeView.PaddingRight)
|
||||
&& y >= TypedNativeView.PaddingTop
|
||||
&& y <= (TypedNativeView.Height - TypedNativeView.PaddingBottom)
|
||||
&& (VirtualView.FlowDirection == FlowDirection.LeftToRight))
|
||||
|| (x >= (TypedNativeView.Left + TypedNativeView.PaddingLeft)
|
||||
&& x <= (TypedNativeView.Left + rBounds.Width())
|
||||
&& y >= TypedNativeView.PaddingTop
|
||||
&& y <= (TypedNativeView.Height - TypedNativeView.PaddingBottom)
|
||||
&& VirtualView.FlowDirection == FlowDirection.RightToLeft)))
|
||||
{
|
||||
TypedNativeView.Text = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class TextWatcher : Java.Lang.Object, ITextWatcher
|
||||
|
@ -124,5 +212,26 @@ namespace Microsoft.Maui.Handlers
|
|||
Handler?.OnTextChanged(s?.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe better to have generic version in IAndroidViewHandler?
|
||||
class EntryTouchListener : Java.Lang.Object, IOnTouchListener
|
||||
{
|
||||
public EntryHandler? Handler { get; set; }
|
||||
|
||||
public bool OnTouch(View? v, MotionEvent? e)
|
||||
{
|
||||
return Handler?.OnTouch(e) ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe better to have generic version in IAndroidViewHandler?
|
||||
class EntryFocusChangeListener : Java.Lang.Object, IOnFocusChangeListener
|
||||
{
|
||||
public EntryHandler? Handler { get; set; }
|
||||
public void OnFocusChange(View? v, bool hasFocus)
|
||||
{
|
||||
Handler?.OnFocusedChange(hasFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ namespace Microsoft.Maui.Handlers
|
|||
public static void MapIsReadOnly(IViewHandler handler, IEntry entry) { }
|
||||
public static void MapFont(IViewHandler handler, IEntry entry) { }
|
||||
public static void MapReturnType(IViewHandler handler, IEntry entry) { }
|
||||
public static void MapClearButtonVisibility(IViewHandler handler, IEntry entry) { }
|
||||
public static void MapCharacterSpacing(IViewHandler handler, IEntry entry) { }
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
[nameof(IEntry.IsReadOnly)] = MapIsReadOnly,
|
||||
[nameof(IEntry.Font)] = MapFont,
|
||||
[nameof(IEntry.ReturnType)] = MapReturnType,
|
||||
[nameof(IEntry.ClearButtonVisibility)] = MapClearButtonVisibility,
|
||||
[nameof(IEntry.CharacterSpacing)] = MapCharacterSpacing
|
||||
};
|
||||
|
||||
|
|
|
@ -119,6 +119,11 @@ namespace Microsoft.Maui.Handlers
|
|||
handler.TypedNativeView?.UpdateCharacterSpacing(entry);
|
||||
}
|
||||
|
||||
public static void MapClearButtonVisibility(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
handler.TypedNativeView?.UpdateClearButtonVisibility(entry);
|
||||
}
|
||||
|
||||
void OnEditingChanged(object? sender, EventArgs e) => OnTextChanged();
|
||||
|
||||
void OnEditingEnded(object? sender, EventArgs e) => OnTextChanged();
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using Android.Content.Res;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.Text;
|
||||
using Android.Util;
|
||||
using AndroidX.AppCompat.Widget;
|
||||
|
||||
namespace Microsoft.Maui
|
||||
|
@ -132,8 +134,55 @@ namespace Microsoft.Maui
|
|||
editText.Focusable = isEditable;
|
||||
}
|
||||
|
||||
public static void UpdateFont(this AppCompatEditText editText, IEntry entry, IFontManager fontManager) =>
|
||||
editText.UpdateFont(entry.Font, fontManager);
|
||||
public static void UpdateFont(this AppCompatEditText editText, IEntry entry, IFontManager fontManager)
|
||||
{
|
||||
var font = entry.Font;
|
||||
|
||||
var tf = fontManager.GetTypeface(font);
|
||||
editText.Typeface = tf;
|
||||
|
||||
var sp = fontManager.GetScaledPixel(font);
|
||||
editText.SetTextSize(ComplexUnitType.Sp, sp);
|
||||
}
|
||||
|
||||
public static void UpdateClearButtonVisibility(this AppCompatEditText editText, IEntry entry, Drawable? ClearButtonDrawable)
|
||||
{
|
||||
// Places clear button drawable at the end or start of the EditText based on FlowDirection.
|
||||
void ShowClearButton()
|
||||
{
|
||||
if (entry.FlowDirection == FlowDirection.RightToLeft)
|
||||
{
|
||||
editText.SetCompoundDrawablesWithIntrinsicBounds(ClearButtonDrawable, null, null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
editText.SetCompoundDrawablesWithIntrinsicBounds(null, null, ClearButtonDrawable, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Hides clear button drawable from the control.
|
||||
void HideClearButton()
|
||||
{
|
||||
editText.SetCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
|
||||
}
|
||||
|
||||
bool isFocused = editText.IsFocused;
|
||||
bool hasText = entry.Text?.Length > 0;
|
||||
|
||||
bool shouldDisplayClearButton = entry.ClearButtonVisibility == ClearButtonVisibility.WhileEditing
|
||||
&& hasText
|
||||
&& isFocused;
|
||||
|
||||
if (shouldDisplayClearButton)
|
||||
{
|
||||
ShowClearButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
HideClearButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void UpdateReturnType(this AppCompatEditText editText, IEntry entry)
|
||||
{
|
||||
|
@ -174,4 +223,4 @@ namespace Microsoft.Maui
|
|||
? currentText.Substring(0, maxLength)
|
||||
: currentText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,5 +104,10 @@ namespace Microsoft.Maui
|
|||
var uiFont = fontManager.GetFont(textView.Font);
|
||||
textField.Font = uiFont;
|
||||
}
|
||||
|
||||
public static void UpdateClearButtonVisibility(this UITextField textField, IEntry entry)
|
||||
{
|
||||
textField.ClearButtonMode = entry.ClearButtonVisibility == ClearButtonVisibility.WhileEditing ? UITextFieldViewMode.WhileEditing : UITextFieldViewMode.Never;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.Text;
|
||||
using Android.Views.InputMethods;
|
||||
using AndroidX.AppCompat.Widget;
|
||||
|
@ -143,6 +145,24 @@ namespace Microsoft.Maui.DeviceTests
|
|||
ImeAction GetNativeReturnType(EntryHandler entryHandler) =>
|
||||
GetNativeEntry(entryHandler).ImeOptions;
|
||||
|
||||
bool GetNativeClearButtonVisibility(EntryHandler entryHandler)
|
||||
{
|
||||
var nativeEntry = GetNativeEntry(entryHandler);
|
||||
var unfocusedDrawables = nativeEntry.GetCompoundDrawables();
|
||||
|
||||
bool compoundsValidWhenUnfocused = !unfocusedDrawables.Any(a => a != null);
|
||||
|
||||
// This will display 'X' drawable.
|
||||
nativeEntry.RequestFocus();
|
||||
|
||||
var focusedDrawables = nativeEntry.GetCompoundDrawables();
|
||||
|
||||
// Index 2 for FlowDirection.LeftToRight.
|
||||
bool compoundsValidWhenFocused = focusedDrawables.Length == 4 && focusedDrawables[2] != null;
|
||||
|
||||
return compoundsValidWhenFocused && compoundsValidWhenUnfocused;
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "CharacterSpacing Initializes Correctly")]
|
||||
public async Task CharacterSpacingInitializesCorrectly()
|
||||
{
|
||||
|
|
|
@ -193,6 +193,21 @@ namespace Microsoft.Maui.DeviceTests
|
|||
await ValidatePropertyInitValue(entry, () => entry.Font.FontAttributes.HasFlag(FontAttributes.Italic), GetNativeIsItalic, isItalic);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "Validates clear button visibility.")]
|
||||
[InlineData(ClearButtonVisibility.WhileEditing, true)]
|
||||
[InlineData(ClearButtonVisibility.Never, false)]
|
||||
public async Task ValidateClearButtonVisibility(ClearButtonVisibility clearButtonVisibility, bool expected)
|
||||
{
|
||||
var entryStub = new EntryStub()
|
||||
{
|
||||
ClearButtonVisibility = clearButtonVisibility,
|
||||
Text = "Test text input.",
|
||||
FlowDirection = FlowDirection.LeftToRight
|
||||
};
|
||||
|
||||
await ValidatePropertyInitValue(entryStub, () => expected, GetNativeClearButtonVisibility, expected);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "TextChanged Events Fire Correctly")]
|
||||
// null/empty
|
||||
[InlineData(null, null, false)]
|
||||
|
|
|
@ -149,6 +149,9 @@ namespace Microsoft.Maui.DeviceTests
|
|||
bool GetNativeIsItalic(EntryHandler entryHandler) =>
|
||||
GetNativeEntry(entryHandler).Font.FontDescriptor.SymbolicTraits.HasFlag(UIFontDescriptorSymbolicTraits.Italic);
|
||||
|
||||
bool GetNativeClearButtonVisibility(EntryHandler entryHandler) =>
|
||||
GetNativeEntry(entryHandler).ClearButtonMode == UITextFieldViewMode.WhileEditing;
|
||||
|
||||
UITextAlignment GetNativeTextAlignment(EntryHandler entryHandler) =>
|
||||
GetNativeEntry(entryHandler).TextAlignment;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace Microsoft.Maui.DeviceTests.Stubs
|
|||
public TextAlignment HorizontalTextAlignment { get; set; }
|
||||
|
||||
public ReturnType ReturnType { get; set; }
|
||||
public ClearButtonVisibility ClearButtonVisibility { get; set; }
|
||||
|
||||
public event EventHandler<StubPropertyChangedEventArgs<string>> TextChanged;
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче