Quantcast
Channel: Xamarin.Forms — Xamarin Community Forums
Viewing all articles
Browse latest Browse all 91519

[Material] TextInputLayout Renderer (Floating Labels)

$
0
0

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

image

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).


Viewing all articles
Browse latest Browse all 91519

Trending Articles