EDIT:
I've now written a control for iOS and Android (also includes an AutoComplete/ComboBox)
Install-Package Xfx.Controls
Or the source: https://github.com/XamFormsExtended/Xfx.Controls
ORIGINAL POST:
Just thought I'd throw this out there. If you want floating labels on your Entry (as found here (by JamesMontemagno)), I've thrown together a custom renderer that extends Entry
.
note: this requires the AppCompat stuff to be enabled in your Android App.
First you want to create an axml view and drop it in your /Resources/layout
directory.
TextInputLayout.axml
<!-- NOTE: MyAppCompatTheme is important - it MUST apply an AppCompat theme of some sort -->
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.TextInputLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textInputLayout"
android:theme="@style/MyAppCompatTheme">
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
From there, just drop this renderer into your Droid Renderers directory (wherever that lives).
MaterialEntryRenderer_Droid.cs
using System;
using System.ComponentModel;
using Android.Content;
using Android.Support.Design.Widget;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using MyApp.Droid.Extensions;
using MyApp.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Application = Android.App.Application;
using TextChangedEventArgs = Android.Text.TextChangedEventArgs;
[assembly: ExportRenderer(typeof(MaterialEntry), typeof(MaterialEntryRenderer_Droid))]
namespace MyApp.Droid.Renderers
{
public class MaterialEntryRenderer_Droid : Xamarin.Forms.Platform.Android.AppCompat.ViewRenderer<MaterialEntry, TextInputLayout>
{
private EditText _defaultEditTextForValues;
private bool _preventTextLoop;
protected override void OnElementChanged(ElementChangedEventArgs<MaterialEntry> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// unsubscribe
if (Element != null)
{
Element.HideKeyboard -= ElementOnHideKeyboard;
}
Control.EditText.KeyPress -= EditTextOnKeyPress;
Control.EditText.TextChanged -= EditTextOnTextChanged;
}
if (e.NewElement != null)
{
var ctrl = CreateNativeControl();
SetNativeControl(ctrl);
_defaultEditTextForValues = new EditText(Context);
SetText();
SetHintText();
SetTextColor();
SetBackgroundColor();
SetHintColor();
SetIsPassword();
SetKeyboard();
// Subscribe
Control.EditText.TextChanged += EditTextOnTextChanged;
Control.EditText.KeyPress += EditTextOnKeyPress;
if (Element != null)
{
Element.HideKeyboard += ElementOnHideKeyboard;
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.PlaceholderProperty.PropertyName)
{
SetHintText();
}
if (e.PropertyName == Entry.TextColorProperty.PropertyName)
{
SetTextColor();
}
if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
{
SetBackgroundColor();
}
if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
{
SetIsPassword();
}
if (e.PropertyName == Entry.TextProperty.PropertyName)
{
SetText();
}
if (e.PropertyName == Entry.PlaceholderColorProperty.PropertyName)
{
SetHintColor();
}
if (e.PropertyName == InputView.KeyboardProperty.PropertyName)
{
SetKeyboard();
}
}
private void ElementOnHideKeyboard(object sender, EventArgs eventArgs)
{
var manager = (InputMethodManager)Application.Context.GetSystemService(Context.InputMethodService);
manager.HideSoftInputFromWindow(Control.EditText.WindowToken, 0);
}
private void SetIsPassword()
{
Control.EditText.InputType = Element.IsPassword
? InputTypes.TextVariationPassword | InputTypes.ClassText
: Control.EditText.InputType;
}
private void SetBackgroundColor()
{
Control.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
}
private void SetHintText()
{
Control.Hint = Element.Placeholder;
}
private void SetHintColor()
{
if (Element.PlaceholderColor == Color.Default)
{
Control.EditText.SetHintTextColor(_defaultEditTextForValues.HintTextColors);
}
else
{
Control.EditText.SetHintTextColor(Element.PlaceholderColor.ToAndroid());
}
}
private void SetTextColor()
{
if (Element.TextColor == Color.Default)
{
Control.EditText.SetTextColor(_defaultEditTextForValues.TextColors);
}
else
{
Control.EditText.SetTextColor(Element.TextColor.ToAndroid());
}
}
private void SetKeyboard()
{
Control.EditText.InputType = Element.Keyboard.ToNative();
}
protected override TextInputLayout CreateNativeControl()
{
var layout = (TextInputLayout)LayoutInflater.From(Context).Inflate(Resource.Layout.TextInputLayout, null);
var inner = layout.FindViewById(Resource.Id.textInputEdit);
if (!string.IsNullOrWhiteSpace(Element.AutomationId))
{
inner.ContentDescription = Element.AutomationId;
}
return layout;
}
private void EditTextOnKeyPress(object sender, KeyEventArgs args)
{
args.Handled = args.KeyCode == Keycode.Enter;
if (args.KeyCode == Keycode.Enter && args.Event.Action == KeyEventActions.Up)
{
Element.OnCompleted(this, EventArgs.Empty);
}
}
private void EditTextOnTextChanged(object sender, TextChangedEventArgs args)
{
// As I type: send the EditText to the Forms Entry
var selection = Control.EditText.SelectionStart;
if (!_preventTextLoop)
{
Element.Text = args.Text.ToString();
}
if (Element == null || Element.Text == null) return;
var index = selection > Element.Text.Length ? Element.Text.Length : selection;
Control.EditText.SetSelection(index);
}
private void SetText()
{
_preventTextLoop = true;
if (Control.EditText.Text != Element.Text)
{
// If I programmatically change text on the Forms Entry,
// send the forms entry to the native EditText
Control.EditText.Text = Element.Text;
}
_preventTextLoop = false;
}
protected override void Dispose(bool disposing)
{
// this is here because the PopupPage disposes of the object in a weird way.
// unsubscribe
if (Element != null)
{
Element.HideKeyboard -= ElementOnHideKeyboard;
}
Control.EditText.KeyPress -= EditTextOnKeyPress;
Control.EditText.TextChanged -= EditTextOnTextChanged;
base.Dispose(disposing);
}
}
}
KeyboardExtensions.cs
using Android.Text;
using Xamarin.Forms;
namespace MyAppDroid.Extensions
{
public static class KeyboardExtensions
{
public static InputTypes ToNative(this Keyboard input)
{
if (input == Keyboard.Url)
{
return InputTypes.ClassText | InputTypes.TextVariationUri;
}
if (input == Keyboard.Email)
{
return InputTypes.ClassText | InputTypes.TextVariationEmailAddress;
}
if (input == Keyboard.Numeric)
{
return InputTypes.ClassNumber;
}
if (input == Keyboard.Chat)
{
return InputTypes.ClassText | InputTypes.TextVariationShortMessage;
}
if (input == Keyboard.Telephone)
{
return InputTypes.ClassPhone;
}
if (input == Keyboard.Text)
{
return InputTypes.ClassText | InputTypes.TextFlagNoSuggestions;
}
return InputTypes.ClassText;
}
}
}
Disclaimer: I am not responsible for your issues - if you have questions or enhancements, post them below.
This renderer currently only supports the following properties.
- TextColor
- Placeholder
- IsPassword
- BackgroundColor
feel free to add more
also, I didn't actually test binding... I did read somewhere that OnElementPropertyChanged
doesn't fire on a ViewRenderer
if you don't explicitly add the event hooks... that could have been a bug, I don't know. I just statically set these properties (for now).