Textinputlayout error kotlin

Создание выпадающего меню

Создание выпадающего меню

Макет TextInputLayout сначала появился в библиотеке Android Design Support Library и добавляет немного красоты к текстовому полю. Когда пользователь начинает вводить текст в текстовом поле, то подсказка, заданная в этом компоненте, всплывает над ним в специальном TextView. Пример можно увидеть на видео.

Библиотека больше не развивается, поэтому используйте AndroidX, которую я и буду теперь использовать в описании.

Компонент можно найти на панели инструментов в разделе Text.

При добавлении компонента через визуальный редактор автоматически добавляется дочерний элемент TextInputEditText.


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Main2Activity">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="hint" />
    </com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

Так выглядел компонент в составе Support Library.

Подсказку не обязательно указывать в атрибуте android:hint у текстового поля. Можно программно присвоить через метод:


textInputLayout.setHint(getString(R.string.hint));

Стилизация

В AndroidX у компонента появились новые стили: OutlinedBox, FilledBox и другие. Можете самостоятельно попробовать пример с двумя стилями и посмотреть на отличия.


<com.google.android.material.textfield.TextInputLayout
    style="@style/Widget.MaterialComponents.TextInputEditText.OutlinedBox"
    ... />

<com.google.android.material.textfield.TextInputLayout
    style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
    ... />

Общая стилизация доступна следующим образом. Пропишем стили в styles.xml:


<!--Floating label text style-->  
<style name="MyHintText" parent="TextAppearance.AppCompat.Small">  
    <item name="android:textColor">@color/pink</item>
</style>

<!--Input field style-->  
<style name="MyEditText" parent="Theme.AppCompat.Light">  
    <item name="colorControlNormal">@color/indigo</item>
    <item name="colorControlActivated">@color/pink</item>
</style>  

Применим стили


<com.google.android.material.textfield.TextInputLayout  
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:hintTextAppearance="@style/MyHintText">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/Title"
        android:theme="@style/MyEditText" />

</com.google.android.material.textfield.TextInputLayout>

Обработка ошибки

Предыдущий пример показывал применение подсказки. Но также можно выводить сообщения об ошибке. Здесь потребуется написать немного кода. За вывод ошибки отвечает атрибут app:errorEnabled. Назначим текстовому полю тип клавиатуры и однострочный текст. При наборе текста после нажатия на клавишу Enter проверяем длину текста. Если текст меньше четырёх символов, то выводим сообщение об ошибке.


Добавить пространство имён к корневому элементу
xmlns:app="http://schemas.android.com/apk/res-auto"

<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/textInputLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:errorEnabled="true">

    <EditText
        android:id="@+id/editTextName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:imeOptions="actionGo"
        android:inputType="text"
        android:singleLine="true"/>
</com.google.android.material.textfield.TextInputLayout>

Код


package ru.alexanderklimov.design;

import ...

public class MainActivity extends AppCompatActivity {

    private static final int MIN_TEXT_LENGTH = 4;
    private static final String EMPTY_STRING = "";

    private TextInputLayout mTextInputLayout;
    private EditText mEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mTextInputLayout = findViewById(R.id.textInputLayout);
        mEditText = findViewById(R.id.editTextName);

        mTextInputLayout.setHint(getString(R.string.hint));
        mEditText.setOnEditorActionListener(ActionListener.newInstance(this));
    }

    private boolean shouldShowError() {
        int textLength = mEditText.getText().length();
        return textLength > 0 && textLength < MIN_TEXT_LENGTH;
    }

    private void showError() {
        mTextInputLayout.setError(getString(R.string.error));
    }

    private void hideError() {
        mTextInputLayout.setError(EMPTY_STRING);
    }

    private static final class ActionListener implements TextView.OnEditorActionListener {
        private final WeakReference<MainActivity> mainActivityWeakReference;

        public static ActionListener newInstance(MainActivity mainActivity) {
            WeakReference<MainActivity> mainActivityWeakReference = new WeakReference<>(mainActivity);
            return new ActionListener(mainActivityWeakReference);
        }

        private ActionListener(WeakReference<MainActivity> mainActivityWeakReference) {
            this.mainActivityWeakReference = mainActivityWeakReference;
        }

        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
            MainActivity mainActivity = mainActivityWeakReference.get();
            if (mainActivity != null) {
                if (actionId == EditorInfo.IME_ACTION_GO && mainActivity.shouldShowError()) {
                    mainActivity.showError();
                } else {
                    mainActivity.hideError();
                }
            }
            return true;
        }
    }
}

Текст ошибки выводится снизу от текстового поля.

TextInputLayout

Стиль для сообщения об ошибке можно стилизовать. Добавим новый атрибут.


app:errorEnabled="true"
app:errorTextAppearance="@style/ErrorText"

В файле стилей res/values/styles.xml добавим новый стиль:


<style name="ErrorText" parent="TextAppearance.AppCompat.Small">
    <item name="android:textStyle">bold|italic</item>
    <item name="android:textColor">@color/colorPrimary</item>
</style>

Теперь выводится другим цветом.

TextInputLayout

Расширенный вид стилей:


<!--Error label text style-->  
<style name="MyErrorText" parent="TextAppearance.AppCompat.Small">  
    <item name="android:textColor">@color/colorPrimary</item>
</style>

<!--Input field style-->  
<style name="MyEditText" parent="Theme.AppCompat.Light">  
    <item name="colorControlNormal">@color/indigo</item>
    <item name="colorControlActivated">@color/pink</item>
</style>  

Применяем через атрибуты app:errorTextAppearance и android:theme.


<com.google.android.material.textfield.TextInputLayout  
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:errorTextAppearance="@style/MyErrorText"
    app:errorEnabled="true">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/Title"
        android:theme="@style/MyEditText" />

</com.google.android.material.textfield.TextInputLayout>

Счётчик символов

С помощью атрибутов app:counterEnabled и app:counterMaxLength можно установить счётчик символов с указанием предела, который будет выводиться под текстовым полем.


<com.google.android.material.textfield.TextInputLayout
    ...
    app:counterEnabled="true"
    app:counterMaxLength="140">

    <EditText
        .../>

</com.google.android.material.textfield.TextInputLayout>

Counter TextInputLayout

Когда будет превышен лимит, то цвет счётчика изменится. Этот цвет можно стилизовать через стиль.


<style name="MyCounterText" parent="TextAppearance.AppCompat.Small">
    <item name="android:textColor">@android:color/holo_orange_dark</item>
</style>

Стиль применяется к атрибуту app:counterOverflowTextAppearance:


<com.google.android.material.textfield.TextInputLayout
    ...
    app:counterOverflowTextAppearance="@style/MyCounterText"
    app:counterEnabled="true"
    app:counterMaxLength="10">

Связка TextInputLayout и AutoCompleteTextView позволяет создать выпадающее меню взамен стандартного компонента Spinner. Для этого задействуем один стилей, в котором присутствует ExposedDropdownMenu.

TextInputLayout ExposedDropdownMenu

Разметка экрана.


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.textfield.TextInputLayout
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <AutoCompleteTextView
            android:id="@+id/autoCompleteTextView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:inputType="none"
            android:text="Барсик" />
    </com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

В AutoCompleteTextView установлен тип android:inputType=»none», чтобы у пользователя не было возможности изменять текст. Ведь нам нужно меню, а не текстовое поле.

Для создания элементов меню воспользуемся массивом строк в ресурсах. Добавляем в res/values/strings.xml.


<string-array name="cats">
    <item>Барсик</item>
    <item>Мурзик</item>
    <item>Рыжик</item>
    <item>Васька</item>
</string-array>

Создадим отдельную разметку для элементов меню в файле res/layout/dropdown_item.xml.


<?xml version="1.0" encoding="utf-8"?>
<TextView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="14dp"
    android:textStyle="bold">
</TextView>

Присоединяем созданный макет к AutoCompleteTextView через адаптер.


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val autoCompleteTextView: AutoCompleteTextView = findViewById(R.id.autoCompleteTextView)

    val cats = resources.getStringArray((R.array.cats))
    val arrayAdapter = ArrayAdapter(this, R.layout.dropdown_item, cats)

    autoCompleteTextView.setAdapter(arrayAdapter)
}

Посмотрим на результат. Нажимаем на AutoCompleteTextView и получаем выпадающий список из массива строк.

TextInputLayout ExposedDropdownMenu

Мы получили рабочий прототип, но при повороте меню работать не будет. Проверьте самостоятельно. Поэтому немного поправим код.


class MainActivity : AppCompatActivity() {

    private lateinit var autoCompleteTextView: AutoCompleteTextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        autoCompleteTextView = findViewById(R.id.autoCompleteTextView)
    }

    override fun onResume() {
        super.onResume()

        val cats = resources.getStringArray((R.array.cats))
        val arrayAdapter = ArrayAdapter(this, R.layout.dropdown_item, cats)

        autoCompleteTextView.setAdapter(arrayAdapter)
    }
}

Можно добавить к элементам меню значок. Заодно добавим подсказку.


<com.google.android.material.textfield.TextInputLayout
    style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginEnd="16dp"
    android:hint="Коты"
    app:startIconDrawable="@drawable/ic_pets"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

TextInputLayout ExposedDropdownMenu

TextInputEditText

Казалось бы простой компонент, никаких трудностей не возникает. Но не торопитесь. Стоит повернуть устройство в альбомный режим, как текстовое поле растянется на весь экран и никакой подсказки вы не увидите. Возможно, это баг, который когда-нибудь починят. Но проблему легко решить, если вместо стандартного EditText использовать специальный компонент TextInputEditText:


<com.google.android.material.textfield.TextInputLayout
    ...>

    <com.google.android.material.textfield.TextInputEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Введите текст"/>

</com.google.android.material.textfield.TextInputLayout>

Дополнительное чтение

TextInputEditText

Реклама

Text fields

Text fields let users enter and
edit text.

"Text fields on a screen"

Contents

  • Using text fields
  • Filled text field
  • Outlined text field
  • Theming

Using text fields

Before you can use Material text fields, you need to add a dependency to the
Material Components for Android library. For more information, go to the
Getting started
page.

<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/textField"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/label">

    <com.google.android.material.textfield.TextInputEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
    />

</com.google.android.material.textfield.TextInputLayout>

Note: A text field is composed of a TextInputLayout and a
TextInputEditText as a direct child. Using an EditText as the child might
work, but TextInputEditText provides accessibility support for the text field
and allows TextInputLayout greater control over the visual aspects of the
input text. If an EditText is being used, make sure to set its
android:background to @null so that TextInputLayout can set the proper
background on it.

Making text fields accessible

Android’s text field component APIs support both label text and helper text,
which explain what is requested for a text field. While optional, their use is
strongly encouraged.

Content description

When using custom icons, you should set a content description on them so
that screen readers, like TalkBack, are able to announce their purpose or
action.

For the leading icon, that can be achieved via the
app:startIconContentDescription attribute or setStartIconContentDescription
method. For the trailing icon, that can be achieved via the
app:endIconContentDescription attribute or setEndIconContentDescription
method.

When setting an error message that contains special characters that screen
readers or other accessibility systems are not able to read, you should set a
content description via the app:errorContentDescription attribute or
setErrorContentDescription method. That way the error will announce the
content description instead of the error message.

Note: Icons that don’t have click listeners set on them work as decorative
elements, and are therefore skipped by screen readers.

Custom EditText

If you are using a custom EditText as TextInputLayout‘s child and your text
field requires different accessibility support than the one offered by
TextInputLayout, you can set a TextInputLayout.AccessibilityDelegate via the
setTextInputAccessibilityDelegate method. This method should be used in place
of providing an AccessibilityDelegate directly on the EditText.

Adding a leading icon to a text field

"Text field with a leading icon."

<com.google.android.material.textfield.TextInputLayout
    ...
    app:startIconDrawable="@drawable/ic_search_24dp"
    app:startIconContentDescription="@string/content_description_start_icon">

    ...

</com.google.android.material.textfield.TextInputLayout>

Adding a trailing icon to a text field

Password toggle:

"Text field with a password toggle trailing icon."

When the TextInputEditText is set to display a password, an icon can be added
to toggle between masking the password or displaying the password as plain-text.

<com.google.android.material.textfield.TextInputLayout
    ...
    app:endIconMode="password_toggle">

    <com.google.android.material.textfield.TextInputEditText
        ...
        android:inputType="textPassword"
    />

</com.google.android.material.textfield.TextInputLayout>

Clear text:

"Text field with a clear text trailing icon."

An icon can be set to display when text is present. The icon can be pressed to
clear the input text.

<com.google.android.material.textfield.TextInputLayout
    ...
    app:endIconMode="clear_text">

    ...

</com.google.android.material.textfield.TextInputLayout>

Custom icon:

"Text field with a custom trailing icon."

It is possible to set a custom Drawable as the text field’s trailing icon via
app:endIconMode="custom". You should specify a drawable and content
description for the icon, and you have the option to specify custom behaviors.

In the layout:

<com.google.android.material.textfield.TextInputLayout
    ...
    app:endIconMode="custom"
    app:endIconDrawable="@drawable/ic_accelerator_24dp"
    app:endIconContentDescription="@string/content_description_end_icon">

    ...

</com.google.android.material.textfield.TextInputLayout>

Optionally, in code:

textField.setEndIconOnClickListener {
  // Respond to end icon presses
}

textField.addOnEditTextAttachedListener {
  // If any specific changes should be done when the edit text is attached (and
  // thus when the trailing icon is added to it), set an
  // OnEditTextAttachedListener.

  // Example: The clear text icon's visibility behavior depends on whether the
  // EditText has input present. Therefore, an OnEditTextAttachedListener is set
  // so things like editText.getText() can be called.
}

textField.addOnEndIconChangedListener {
  // If any specific changes should be done if/when the endIconMode gets
  // changed, set an OnEndIconChangedListener.

  // Example: If the password toggle icon is set and a different EndIconMode
  // gets set, the TextInputLayout has to make sure that the edit text's
  // TransformationMethod is still PasswordTransformationMethod. Because of
  // that, an OnEndIconChangedListener is used.
}

Note: You should opt to use the EndIconMode API instead of setting an
end/right compound Drawable on the TextInputEditText. The same applies to
the now-deprecated passwordToggle* attributes.

Important: Calling setEndIconMode will initialize the icon with its
default features, such as default drawables, and in the case of the custom mode,
an empty drawable. You can add customizations after calling setEndIconMode.
The exception for this is if a drawable was specified in XML via the
app:endIconDrawable attribute. An end icon drawable set in XML will take
precedence and override an existing default icon.

See the full list of
end icon modes.

Implementing an exposed dropdown menu

"Text field with an exposed dropdown menu."

In the layout:

<com.google.android.material.textfield.TextInputLayout
    ...
    style="@style/Widget.Material3.TextInputLayout.*.ExposedDropdownMenu">

    <AutoCompleteTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="none"
        app:simpleItems="@array/simple_items"
    />

</com.google.android.material.textfield.TextInputLayout>

The string array specified by app:simpleItems will be used as the default
item strings for auto-completion. Or you can also set it programmatically:

val items = arrayOf("Item 1", "Item 2", "Item 3", "Item 4")
(textField.editText as? MaterialAutoCompleteTextView)?.setSimpleItems(items)

Alternatively, to have more control over the auto-completion items rendering,
you can also provide a custom item adapter by:

val items = listOf("Item 1", "Item 2", "Item 3", "Item 4")
val adapter = ArrayAdapter(requireContext(), R.layout.list_item, items)
(textField.editText as? AutoCompleteTextView)?.setAdapter(adapter)

And a custom item layout (list_item.xml):

<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:ellipsize="end"
    android:maxLines="1"
    android:textAppearance="?attr/textAppearanceTitleMedium"
/>

Adding helper text to a text field

"Text field with helper text."

<com.google.android.material.textfield.TextInputLayout
    ...
    app:helperTextEnabled="true"
    app:helperText="@string/helper_text">

    ...

</com.google.android.material.textfield.TextInputLayout>

Adding a counter to a text field

"Text field with a counter."

<com.google.android.material.textfield.TextInputLayout
    ...
    app:counterEnabled="true"
    app:counterMaxLength="20">

    ...

</com.google.android.material.textfield.TextInputLayout>

Adding errors to a text field

"Text field with an error."

In the layout:

<com.google.android.material.textfield.TextInputLayout
    ...
    app:errorEnabled="true">

    ...

</com.google.android.material.textfield.TextInputLayout>

In code:

// Set error text
passwordLayout.error = getString(R.string.error)

// Clear error text
passwordLayout.error = null

Note: Non-null error text will replace any existing helper text, and
non-null helper text will replace any existing error text.

Adding a prefix/suffix to a text field

"Text field with a prefix/suffix."

<com.google.android.material.textfield.TextInputLayout
    ...
    app:prefixText="@string/prefix"
    app:suffixText="@string/suffix">

    ...

</com.google.android.material.textfield.TextInputLayout>

Text field dimensions

The recommended default android:layout_width is 245dp.

By default, text fields have a maximum width of 488dp, and a minimum width of
56dp for layouts without a label. If a label is present, the minimum width
recommended is 88dp. android:minWidth and android:maxWidth (as well as
android:minEms and android:maxEms) should be set on the TextInputLayout
instead of on the TextInputEditText to avoid unintended behaviors.

You can override those values in a custom style that inherits from a
TextInputLayout style or by making changes directly on the layout:

<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/textField"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:minWidth="@dimen/custom_min_width"
    android:maxWidth="@dimen/custom_max_width"
    android:hint="@string/label">

    <com.google.android.material.textfield.TextInputEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
    />

</com.google.android.material.textfield.TextInputLayout>

Note: The android:layout_width of the TextInputLayout should be
wrap_content in order for those minimum and maximum dimensions to be used.

Using text fields programmatically

If you construct the TextInputEditText child of a TextInputLayout
programmatically, you should use TextInputLayout‘s context to create the view.
This will allow TextInputLayout to pass along the appropriate styling to the
edit text.

val textInputLayout = TextInputLayout(context)
val editText = TextInputEditText(textInputLayout.context)

Types

There are two types of text fields: 1. Filled text field,
2. Outlined text field

"Text field types. Fixed: grey back, dark gray indicator turns purple. Outlined: clear back, gray outline turns purple"

Filled text field

Filled text fields
have more visual emphasis than outlined text fields, making them stand out when
surrounded by other content and components.

Filled text field examples

API and source code:

  • TextInputLayout
    • Class definition
    • Class source
  • TextInputEditText
    • Class definition
    • Class source

The following example shows a filled text field with a label.

Filled text field

In the layout:

<com.google.android.material.textfield.TextInputLayout
    style="?attr/textInputFilledStyle"
    android:id="@+id/filledTextField"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/label">

    <com.google.android.material.textfield.TextInputEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
    />

</com.google.android.material.textfield.TextInputLayout>

In code:

// Get input text
val inputText = filledTextField.editText?.text.toString()

filledTextField.editText?.doOnTextChanged { inputText, _, _, _ ->
    // Respond to input text change
}

See the using text fields section above for more examples.

Anatomy and key properties

A filled text field has a filled container, input text, a label, an activation
indicator, optional helper/error text and optional leading/trailing icons.

Filled text field anatomy

  1. Container
  2. Leading icon
  3. Label
  4. Input text
  5. Trailing icon
  6. Activation indicator
  7. Helper/error/counter text
  8. Prefix/suffix/placeholder (not shown)

Note: All the attributes in the tables below should be set on the
TextInputLayout, with the exception of the input text attributes, which should
be set on the TextInputEditText.

Container attributes

Element Attribute Related method(s) Default value
Color app:boxBackgroundColor setBoxBackgroundColor
setBoxBackgroundColorResource
getBoxBackgroundColor
?attr/colorSurfaceVariant (see all states)
Shape app:shapeAppearance N/A ?attr/shapeAppearanceSmallComponent
Text field enabled android:enabled setEnabled true

Leading icon attributes

Element Attribute Related method(s) Default value
Icon app:startIconDrawable setStartIconDrawable
getStartIconDrawable
null
Content description app:startIconContentDescription setStartIconContentDescription
getStartIconContentDescription
null
Color app:startIconTint setStartIconTintList ?attr/colorOnSurfaceVariant (see all states)
Checkable app:startIconCheckable setStartIconCheckable
isStartIconCheckable
false
Size app:startIconMinSize setStartIconMinSize
getStartIconMinSize
48dp
Scale type app:startIconScaleType setStartIconScaleType
getStartIconScaleType
ScaleType.CENTER

Label attributes

Element Attribute Related method(s) Default value
Text android:hint setHint
getHint
null
Color android:textColorHint setDefaultHintTextColor
getDefaultHintTextColor
?attr/colorOnSurfaceVariant (see all states)
Collapsed (floating) color app:hintTextColor setHintTextColor
getHintTextColor
?attr/colorPrimary (see all states)
Typography app:hintTextAppearance setHintTextAppearance ?attr/textAppearanceBodySmall
Animation app:hintAnimationEnabled setHintAnimationEnabled
isHintAnimationEnabled
true
Expanded enabled app:expandedHintEnabled setExpandedHintEnabled
isExpandedHintEnabled
true

Note: The android:hint should always be set on the TextInputLayout
instead of on the EditText in order to avoid unintended behaviors.

Input text attributes

Element Attribute Related method(s) Default value
Input text android:text setText
getText
@null
Typography android:textAppearance setTextAppearance ?attr/textAppearanceBodyLarge
Input text color android:textColor setTextColor
getTextColors
getCurrentTextColor
?attr/colorOnSurface
Cursor color N/A (color comes from the theme attr ?attr/colorControlActivated) N/A ?attr/colorPrimary
Text highlight color N/A (color comes from the theme attr ?android:attr/textColorHighlight) N/A @color/m3_highlighted_text

Note: The input text attributes should be set on the TextInputEditText.

Trailing icon attributes

Element Attribute Related method(s) Default value
Mode app:endIconMode setEndIconMode
getEndIconMode
END_ICON_NONE
Color app:endIconTint setEndIconTintList colorOnSurfaceVariant (see all states)
Custom icon app:endIconDrawable setEndIconDrawable
getEndIconDrawable
null
Custom icon content description app:endIconContentDescription setEndIconContentDescription
getEndIconContentDescription
null
Custom icon checkable app:endIconCheckable setEndIconCheckable
isEndIconCheckable
true
Error icon app:errorIconDrawable setErrorIconDrawable
getErrorIconDrawable
@drawable/mtrl_ic_error
Error icon color app:errorIconTint setErrorIconTintList ?attr/colorError
Size app:endIconMinSize setEndIconMinSize
getEndIconMinSize
48dp
Scale type app:endIconScaleType setEndIconScaleType
getEndIconScaleType
ScaleType.CENTER

Activation indicator attributes

Element Attribute Related method(s) Default value
Color app:boxStrokeColor setBoxStrokeColor
setBoxStrokeColorStateList
getBoxStrokeColor
?attr/colorOutline and ?attr/colorPrimary (focused) (see all states)
Error color app:boxStrokeErrorColor setBoxStrokeErrorColor
getBoxStrokeErrorColor
?attr/colorError
Width app:boxStrokeWidth N/A 1dp
Focused width app:boxStrokeWidthFocused N/A 2dp

Helper/error/counter text attributes

Element Attribute Related method(s) Default value
Helper text enabled app:helperTextEnabled setHelperTextEnabled
isHelperTextEnabled
false
Helper text app:helperText setHelperText
getHelperText
null
Helper text color app:helperTextColor setHelperTextColor
getHelperTextColor
?attr/colorOnSurfaceVariant (see all states)
Helper text typography app:helperTextAppearance setHelperTextAppearance ?attr/textAppearanceBodySmall
Error text enabled app:errorEnabled setErrorEnabled
isErrorEnabled
false
Error text N/A setError
getError
null
Error text accessibility live region app:errorAccessibilityLiveRegion setErrorAccessibilityLiveRegion
getErrorAccessibilityLiveRegion
ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE
Error text color app:errorTextColor setErrorTextColor
getErrorCurrentTextColors
?attr/colorError
Error text typography app:errorTextAppearance setErrorTextAppearance ?attr/textAppearanceBodySmall
Counter text enabled app:counterEnabled setCounterEnabled
isCounterEnabled
false
Counter text length app:counterMaxLength setCounterMaxLength
getCounterMaxLength
-1
Counter text typography app:counterTextAppearance
app:counterOverflowTextAppearance
setCounterTextAppearance
setCounterOverflowTextAppearance
?attr/textAppearanceBodySmall
Counter text color app:counterTextColor
app:counterOverflowTextColor
setCounterTextColor
setCounterOverflowTextColor
getCounterTextColor
getCounterOverflowTextColor
?attr/colorOnSurfaceVariant (app:counterTextColor) (see all states)
?attr/colorError (app:counterOverflowTextColor)

Prefix/suffix attributes

Element Attribute Related method(s) Default value
Prefix app:prefixText setPrefixText
getPrefixText
null
Prefix color app:prefixTextColor setPrefixTextColor
getPrefixTextColor
?attr/colorOnSurfaceVariant (see all states)
Prefix typography app:prefixTextAppearance setPrefixTextAppearance ?attr/textAppearanceTitleMedium
Suffix app:suffixText setSuffixText
getSuffixText
null
Suffix color app:suffixTextColor setSuffixTextColor
getSuffixTextColor
?attr/colorOnSurfaceVariant (see all states)
Suffix typography app:suffixTextAppearance setSuffixTextAppearance ?attr/textAppearanceTitleMedium

Styles

Element Style Default style theme attribute
Default style Widget.Material3.TextInputLayout.FilledBox ?attr/textInputFilledStyle
Dense style Widget.Material3.TextInputLayout.FilledBox.Dense ?attr/textInputFilledDenseStyle
Exposed dropdown menu style Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu ?attr/textInputFilledExposedDropdownMenuStyle
Dense exposed dropdown menu style Widget.Material3.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu N/A

See the full list of
styles
and
attrs.

Outlined text field

Outlined text fields
have less visual emphasis than filled text fields. When they appear in forms,
for example, where many text fields are placed together, their reduced emphasis
helps simplify the layout.

Note: The outlined text field is the default style.

Outlined text field examples

API and source code:

  • TextInputLayout
    • Class definition
    • Class source
  • TextInputEditText
    • Class definition
    • Class source

The following example shows an outlined text field.

Outlined text field

In the layout:

<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/outlinedTextField"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/label">

    <com.google.android.material.textfield.TextInputEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
    />

</com.google.android.material.textfield.TextInputLayout>

In code:

// Get input text
val inputText = outlinedTextField.editText?.text.toString()

outlinedTextField.editText?.doOnTextChanged { inputText, _, _, _ ->
    // Respond to input text change
}

See the using text fields section above for more examples.

Anatomy and key properties

An outlined text field has a stroked container, input text, a label, optional
helper/error text and optional leading/trailing icons.

Outlined text field anatomy

  1. Container
  2. Leading icon
  3. Label
  4. Input text
  5. Trailing icon
  6. Helper/error/counter text
  7. Prefix/suffix/placeholder (not shown)

Note: All the attributes in the tables below should be set on the
TextInputLayout, with the exception of the input text attributes, which should
be set on the TextInputEditText.

Container attributes

Element Attribute Related method(s) Default value
Stroke color app:boxStrokeColor setBoxStrokeColor
setBoxStrokeColorStateList
getBoxStrokeColor
?attr/colorOutline and ?attr/colorPrimary (focused) (see all states)
Stroke error color app:boxStrokeErrorColor setBoxStrokeErrorColor
getBoxStrokeErrorColor
?attr/colorError
Stroke width app:boxStrokeWidth N/A 1dp
Stroke focused width app:boxStrokeWidthFocused N/A 2dp
Shape app:shapeAppearance N/A ?attr/shapeAppearanceSmallComponent
Text field enabled android:enabled setEnabled true

Leading icon attributes

Element Attribute Related method(s) Default value
Icon app:startIconDrawable setStartIconDrawable
getStartIconDrawable
null
Content description app:startIconContentDescription setStartIconContentDescription
getStartIconContentDescription
null
Color app:startIconTint setStartIconTintList ?attr/colorOnSurfaceVariant (see all states)
Checkable app:startIconCheckable setStartIconCheckable
isStartIconCheckable
false

Label attributes

Element Attribute Related method(s) Default value
Text android:hint setHint
getHint
null
Color android:textColorHint setDefaultHintTextColor
getDefaultHintTextColor
?attr/colorOnSurfaceVariant (see all states)
Collapsed (floating) color app:hintTextColor setHintTextColor
getHintTextColor
?attr/colorPrimary
Typography app:hintTextAppearance setHintTextAppearance ?attr/textAppearanceBodySmall

Note: The android:hint should always be set on the TextInputLayout
instead of on the EditText in order to avoid unintended behaviors.

Input text attributes

Element Attribute Related method(s) Default value
Input text android:text setText
getText
@null
Typography android:textAppearance setTextAppearance ?attr/textAppearanceBodyLarge
Input text color android:textColor setTextColor
getTextColors
getCurrentTextColor
?attr/colorOnSurface
Cursor color N/A (color comes from the theme attr ?attr/colorControlActivated) N/A ?attr/colorPrimary
Text highlight color N/A (color comes from the theme attr ?android:attr/textColorHighlight) N/A @color/m3_highlighted_text

Note: The input text attributes should be set on the TextInputEditText.

Trailing icon attributes

Element Attribute Related method(s) Default value
Mode app:endIconMode setEndIconMode
getEndIconMode
END_ICON_NONE
Color app:endIconTint setEndIconTintList ?attr/colorOnSurfaceVariant (see all states)
Custom icon app:endIconDrawable setEndIconDrawable
getEndIconDrawable
null
Custom icon content description app:endIconContentDescription setEndIconContentDescription
getEndIconContentDescription
null
Custom icon checkable app:endIconCheckable setEndIconCheckable
isEndIconCheckable
true
Error icon app:errorIconDrawable setErrorIconDrawable
getErrorIconDrawable
@drawable/mtrl_ic_error
Error icon color app:errorIconTint setErrorIconTintList ?attr/colorError

Helper/error/counter text attributes

Element Attribute Related method(s) Default value
Helper text enabled app:helperTextEnabled setHelperTextEnabled
isHelperTextEnabled
false
Helper text app:helperText setHelperText
getHelperText
null
Helper text color app:helperTextColor setHelperTextColor
getHelperTextColor
?attr/colorOnSurfaceVariant (see all states)
Helper text typography app:helperTextAppearance setHelperTextAppearance ?attr/textAppearanceBodySmall
Error text enabled app:errorEnabled setErrorEnabled
isErrorEnabled
false
Error text N/A setError
getError
null
Error text accessibility live region app:errorAccessibilityLiveRegion setErrorAccessibilityLiveRegion
getErrorAccessibilityLiveRegion
ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE
Error text color app:errorTextColor setErrorTextColor
getErrorCurrentTextColors
?attr/colorError
Error text typography app:errorTextAppearance setErrorTextAppearance ?attr/textAppearanceBodySmall
Counter text enabled app:counterEnabled setCounterEnabled
isCounterEnabled
false
Counter text length app:counterMaxLength setCounterMaxLength
getCounterMaxLength
-1
Counter text typography app:counterTextAppearance
app:counterOverflowTextAppearance
setCounterTextAppearance
setCounterOverflowTextAppearance
?attr/textAppearanceBodySmall
Counter text color app:counterTextColor
app:counterOverflowTextColor
setCounterTextColor
setCounterOverflowTextColor
getCounterTextColor
getCounterOverflowTextColor
?attr/colorOnSurfaceVariant (app:counterTextColor) (see all states)
?attr/colorError (app:counterOverflowTextColor)

Prefix/suffix attributes

Element Attribute Related method(s) Default value
Prefix app:prefixText setPrefixText
getPrefixText
null
Prefix color app:prefixTextColor setPrefixTextColor
getPrefixTextColor
?attr/colorOnSurfaceVariant (see all states)
Prefix typography app:prefixTextAppearance setPrefixTextAppearance ?attr/textAppearanceTitleMedium
Suffix app:suffixText setSuffixText
getSuffixText
null
Suffix color app:suffixTextColor setSuffixTextColor
getSuffixTextColor
?attr/colorOnSurfaceVariant (see all states)
Suffix typography app:suffixTextAppearance setSuffixTextAppearance ?attr/textAppearanceTitleMedium

Styles

Element Style Default style theme attribute
Default style Widget.Material3.TextInputLayout.OutlinedBox ?attr/textInputStyle and ?attr/textInputOutlinedStyle
Dense style Widget.Material3.TextInputLayout.OutlinedBox.Dense ?attr/textInputOutlinedDenseStyle
Exposed dropdown menu style Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu ?attr/textInputOutlinedExposedDropdownMenuStyle
Dense exposed dropdown menu style Widget.Material3.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu N/A

See the full list of
styles
and
attrs.

Theming text fields

Text fields support
Material Theming which
provides color, typography and shape customization.

Text field theming example

API and source code:

  • TextInputLayout
    • Class definition
    • Class source
  • TextInputEditText
    • Class definition
    • Class source

The following example shows filled and outlined text field types with Material
Theming.

"Filled and outlined text field theming with pink and brown colors and cut corners"

Implementing text field theming

Using theme attributes and styles in res/values/styles.xml adds themes to all
text fields and affects other components:

<style name="Theme.App" parent="Theme.Material3.*">
    ...
    <item name="colorPrimary">@color/shrine_pink_100</item>
    <item name="colorOnSurface">@color/shrine_pink_900</item>
    <item name="colorError">@color/shrine_red</item>
    <item name="textAppearanceTitleMedium">@style/TextAppearance.App.TitleMedium</item>
    <item name="textAppearanceBodySmall">@style/TextAppearance.App.BodySmall</item>
    <item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.App.SmallComponent</item>
</style>

<style name="TextAppearance.App.TitleMedium" parent="TextAppearance.Material3.TitleMedium">
    <item name="fontFamily">@font/rubik</item>
    <item name="android:fontFamily">@font/rubik</item>
</style>

<style name="TextAppearance.App.BodySmall" parent="TextAppearance.Material3.BodySmall">
    <item name="fontFamily">@font/rubik</item>
    <item name="android:fontFamily">@font/rubik</item>
</style>

<style name="ShapeAppearance.App.SmallComponent" parent="ShapeAppearance.Material3.SmallComponent">
    <item name="cornerFamily">cut</item>
    <item name="cornerSize">4dp</item>
</style>

Using default style theme attributes, styles and theme overlays adds themes to
all text fields but does not affect other components:

<style name="Theme.App" parent="Theme.Material3.*">
    ...
    <item name="textInputStyle">@style/Widget.App.TextInputLayout</item>
</style>

<style name="Widget.App.TextInputLayout" parent="Widget.Material3.TextInputLayout.*">
    <item name="materialThemeOverlay">@style/ThemeOverlay.App.TextInputLayout</item>
    <item name="shapeAppearance">@style/ShapeAppearance.App.SmallComponent</item>
    <item name="hintTextColor">?attr/colorOnSurface</item>
</style>

<style name="ThemeOverlay.App.TextInputLayout" parent="">
    <item name="colorPrimary">@color/shrine_pink_100</item>
    <item name="colorOnSurface">@color/shrine_pink_900</item>
    <item name="colorError">@color/shrine_red</item>
    <item name="textAppearanceTitleMedium">@style/TextAppearance.App.TitleMedium</item>
    <item name="textAppearanceBodySmall">@style/TextAppearance.App.BodySmall</item>
    <item name="editTextStyle">@style/Widget.Material3.TextInputEditText.*</item>
</style>

Using the style in the layout affects only this text field:

<com.google.android.material.textfield.TextInputLayout
    ...
    style="@style/Widget.App.TextInputLayout">

    ...

</com.google.android.material.textfield.TextInputLayout>

Note: When setting a materialThemeOverlay on a custom TextInputLayout
style, don’t forget to set editTextStyle to either a
@style/Widget.Material3.TextInputEditText.* style or to a custom one that
inherits from that.
The TextInputLayout styles set
materialThemeOverlay to override editTextStyle with the specific
TextInputEditText style needed. Therefore, you don’t need to specify a style
tag on the edit text.

Время прочтения
10 мин

Просмотры 3.8K

Прямо сейчас в OTUS открыт набор на новый поток курса «Android Developer. Basic». В преддверии старта курса традиционно подготовили для вас интересный перевод, а так же предлагаем посмотреть день открытых дверей по курсу, в рамках которого вы подробно узнаете о процессе обучение и получите ответы на интересующие вопросы.


Удобный способ валидации форм

«Чтобы научиться чему-то хорошо, нужно научиться делать это несколькими способами».

Несколько дней назад я работал над проектом, где мне нужно было реализовать валидацию элементов формы textInputLayout и textInputEditText с помощью связывания данных. К сожалению, доступно не так много документации на эту тему.

В конце концов я добился желаемого результата, изучив кое-какие материалы и проведя ряд экспериментов. Вот что я хотел получить:

Финальный вид приложения
Финальный вид приложения

Уверен, что многие разработчики хотели бы реализовать такой же функционал и удобное взаимодействие с формами. Итак, давайте начнем.

Что нам потребуется?

  1. Kotlin

  2. Статья Связывание данных

  3. Библиотека Material

Я разобью проект на этапы, чтобы легче было понять, что мы делаем.

1. Настроим исходный проект и включим связывание данных в файле build.gradle(:app), добавив под тег android{} следующую строку:

dataBinding{    
    enabled true
}

Для использования элементов textInputLayout и textInputEditText необходимо включить поддержку Material для Android, добавив в файл build.gradle(:app) следующую зависимость:

implementation 'com.google.android.material:material:1.2.1'

Создадим макет нашей формы. Я сделаю простой макет, потому что моя цель — определить его основной функционал, а не создать хороший дизайн.

Я создал вот такой простой макет:

Вот содержимое файла activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        android:orientation="vertical"
        tools:context=".MainActivity">


        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/userNameTextInputLayout"
            style="@style/TextInputLayoutBoxColor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:hint="@string/username"
            app:endIconMode="clear_text"
            app:errorEnabled="true"
            app:hintTextAppearance="@style/TextAppearance.App.TextInputLayout">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/userName"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:inputType="textPersonName" />


        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/emailTextInputLayout"
            style="@style/TextInputLayoutBoxColor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:hint="@string/email"
            app:endIconMode="clear_text"
            app:errorEnabled="true"
            app:hintTextAppearance="@style/TextAppearance.App.TextInputLayout">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/email"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:inputType="textEmailAddress" />


        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/passwordTextInputLayout"
            style="@style/TextInputLayoutBoxColor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:hint="@string/password"
            app:errorEnabled="true"
            app:hintTextAppearance="@style/TextAppearance.App.TextInputLayout"
            app:passwordToggleEnabled="true">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/password"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:inputType="textPassword" />


        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/confirmPasswordTextInputLayout"
            style="@style/TextInputLayoutBoxColor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:hint="@string/confirm_password"
            app:errorEnabled="true"
            app:hintTextAppearance="@style/TextAppearance.App.TextInputLayout"
            app:passwordToggleEnabled="true">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/confirmPassword"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:inputType="textPassword"
                app:passwordToggleEnabled="true" />


        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.button.MaterialButton
            android:id="@+id/loginButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/login"
            android:textAllCaps="false" />

    </LinearLayout>
</layout>

Если вас смущают теги <layout>, не переживайте — о них я написал в своей предыдущей статье.

Наш макет готов. Теперь займемся кодом.

2. На GIF-анимации, показывающей поведение финального варианта приложения (см. выше), видно, как появляются и исчезают сообщения об ошибках, когда заданные условия принимают значение true. Это происходит потому, что я связал каждое текстовое поле с объектом TextWatcher, к которому постоянно происходит обращение по мере ввода текста пользователем.

В файле MainActivity.kt я создал класс, который унаследован от класса TextWatcher:

/**
     * applying text watcher on each text field
     */
    inner class TextFieldValidation(private val view: View) : TextWatcher {
        override fun afterTextChanged(s: Editable?) {}
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            // checking ids of each text field and applying functions accordingly.

        }

    }

Параметр view, который передается в конструктор класса, я опишу позже.

3. Это основная часть. У каждого текстового поля имеется ряд условий, которые должны иметь значение true перед отправкой данных формы. Код, задающий условия для каждого текстового поля, представлен ниже:

  /**
     * field must not be empy
     */
    private fun validateUserName(): Boolean {
        if (binding.userName.text.toString().trim().isEmpty()) {
            binding.userNameTextInputLayout.error = "Required Field!"
            binding.userName.requestFocus()
            return false
        } else {
            binding.userNameTextInputLayout.isErrorEnabled = false
        }
        return true
    }

    /**
     * 1) field must not be empty
     * 2) text should matches email address format
     */
    private fun validateEmail(): Boolean {
        if (binding.email.text.toString().trim().isEmpty()) {
            binding.emailTextInputLayout.error = "Required Field!"
            binding.email.requestFocus()
            return false
        } else if (!isValidEmail(binding.email.text.toString())) {
            binding.emailTextInputLayout.error = "Invalid Email!"
            binding.email.requestFocus()
            return false
        } else {
            binding.emailTextInputLayout.isErrorEnabled = false
        }
        return true
    }

    /**
     * 1) field must not be empty
     * 2) password lenght must not be less than 6
     * 3) password must contain at least one digit
     * 4) password must contain atleast one upper and one lower case letter
     * 5) password must contain atleast one special character.
     */
    private fun validatePassword(): Boolean {
        if (binding.password.text.toString().trim().isEmpty()) {
            binding.passwordTextInputLayout.error = "Required Field!"
            binding.password.requestFocus()
            return false
        } else if (binding.password.text.toString().length < 6) {
            binding.passwordTextInputLayout.error = "password can't be less than 6"
            binding.password.requestFocus()
            return false
        } else if (!isStringContainNumber(binding.password.text.toString())) {
            binding.passwordTextInputLayout.error = "Required at least 1 digit"
            binding.password.requestFocus()
            return false
        } else if (!isStringLowerAndUpperCase(binding.password.text.toString())) {
            binding.passwordTextInputLayout.error =
                "Password must contain upper and lower case letters"
            binding.password.requestFocus()
            return false
        } else if (!isStringContainSpecialCharacter(binding.password.text.toString())) {
            binding.passwordTextInputLayout.error = "1 special character required"
            binding.password.requestFocus()
            return false
        } else {
            binding.passwordTextInputLayout.isErrorEnabled = false
        }
        return true
    }

    /**
     * 1) field must not be empty
     * 2) password and confirm password should be same
     */
    private fun validateConfirmPassword(): Boolean {
        when {
            binding.confirmPassword.text.toString().trim().isEmpty() -> {
                binding.confirmPasswordTextInputLayout.error = "Required Field!"
                binding.confirmPassword.requestFocus()
                return false
            }
            binding.confirmPassword.text.toString() != binding.password.text.toString() -> {
                binding.confirmPasswordTextInputLayout.error = "Passwords don't match"
                binding.confirmPassword.requestFocus()
                return false
            }
            else -> {
                binding.confirmPasswordTextInputLayout.isErrorEnabled = false
            }
        }
        return true
    }

4. Теперь необходимо связать каждое текстовое поле с классом textWatcher, который был создан ранее:

 private fun setupListeners() {
        binding.userName.addTextChangedListener(TextFieldValidation(binding.userName))
        binding.email.addTextChangedListener(TextFieldValidation(binding.email))
        binding.password.addTextChangedListener(TextFieldValidation(binding.password))
        binding.confirmPassword.addTextChangedListener(TextFieldValidation(binding.confirmPassword))
    }

Но как класс TextFieldValidation узнает, с каким текстовым полем нужно связываться? Прокрутив статью выше, вы увидите, что я добавил следующий комментарий в один из методов класса TextFieldValidation:

// проверка идентификаторов текстовых полей и применение соответствующих функций

Обратите внимание, что я передаю параметр view в конструктор класса TextFieldValidation, который отвечает за разделение каждого текстового поля и применение каждого из указанных выше методов следующим образом:

/**
     * applying text watcher on each text field
     */
    inner class TextFieldValidation(private val view: View) : TextWatcher {
        override fun afterTextChanged(s: Editable?) {}
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            // checking ids of each text field and applying functions accordingly.
            when (view.id) {
                R.id.userName -> {
                    validateUserName()
                }
                R.id.email -> {
                    validateEmail()
                }
                R.id.password -> {
                    validatePassword()
                }
                R.id.confirmPassword -> {
                    validateConfirmPassword()
                }
            }
        }
    }

Финальный вариант файла MainActivity.kt выглядит так:

package com.example.textinputlayoutformvalidation

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Patterns
import android.view.View
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import com.example.textinputlayoutformvalidation.FieldValidators.isStringContainNumber
import com.example.textinputlayoutformvalidation.FieldValidators.isStringContainSpecialCharacter
import com.example.textinputlayoutformvalidation.FieldValidators.isStringLowerAndUpperCase
import com.example.textinputlayoutformvalidation.FieldValidators.isValidEmail
import com.example.textinputlayoutformvalidation.databinding.ActivityMainBinding

/**
 * created by : Mustufa Ansari
 * Email : mustufaayub82@gmail.com
 */

class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        setupListeners()

        binding.loginButton.setOnClickListener {
            if (isValidate()) {
                Toast.makeText(this, "validated", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun isValidate(): Boolean =
        validateUserName() && validateEmail() && validatePassword() && validateConfirmPassword()

    private fun setupListeners() {
        binding.userName.addTextChangedListener(TextFieldValidation(binding.userName))
        binding.email.addTextChangedListener(TextFieldValidation(binding.email))
        binding.password.addTextChangedListener(TextFieldValidation(binding.password))
        binding.confirmPassword.addTextChangedListener(TextFieldValidation(binding.confirmPassword))
    }

    /**
     * field must not be empy
     */
    private fun validateUserName(): Boolean {
        if (binding.userName.text.toString().trim().isEmpty()) {
            binding.userNameTextInputLayout.error = "Required Field!"
            binding.userName.requestFocus()
            return false
        } else {
            binding.userNameTextInputLayout.isErrorEnabled = false
        }
        return true
    }

    /**
     * 1) field must not be empty
     * 2) text should matches email address format
     */
    private fun validateEmail(): Boolean {
        if (binding.email.text.toString().trim().isEmpty()) {
            binding.emailTextInputLayout.error = "Required Field!"
            binding.email.requestFocus()
            return false
        } else if (!isValidEmail(binding.email.text.toString())) {
            binding.emailTextInputLayout.error = "Invalid Email!"
            binding.email.requestFocus()
            return false
        } else {
            binding.emailTextInputLayout.isErrorEnabled = false
        }
        return true
    }

    /**
     * 1) field must not be empty
     * 2) password lenght must not be less than 6
     * 3) password must contain at least one digit
     * 4) password must contain atleast one upper and one lower case letter
     * 5) password must contain atleast one special character.
     */
    private fun validatePassword(): Boolean {
        if (binding.password.text.toString().trim().isEmpty()) {
            binding.passwordTextInputLayout.error = "Required Field!"
            binding.password.requestFocus()
            return false
        } else if (binding.password.text.toString().length < 6) {
            binding.passwordTextInputLayout.error = "password can't be less than 6"
            binding.password.requestFocus()
            return false
        } else if (!isStringContainNumber(binding.password.text.toString())) {
            binding.passwordTextInputLayout.error = "Required at least 1 digit"
            binding.password.requestFocus()
            return false
        } else if (!isStringLowerAndUpperCase(binding.password.text.toString())) {
            binding.passwordTextInputLayout.error =
                "Password must contain upper and lower case letters"
            binding.password.requestFocus()
            return false
        } else if (!isStringContainSpecialCharacter(binding.password.text.toString())) {
            binding.passwordTextInputLayout.error = "1 special character required"
            binding.password.requestFocus()
            return false
        } else {
            binding.passwordTextInputLayout.isErrorEnabled = false
        }
        return true
    }

    /**
     * 1) field must not be empty
     * 2) password and confirm password should be same
     */
    private fun validateConfirmPassword(): Boolean {
        when {
            binding.confirmPassword.text.toString().trim().isEmpty() -> {
                binding.confirmPasswordTextInputLayout.error = "Required Field!"
                binding.confirmPassword.requestFocus()
                return false
            }
            binding.confirmPassword.text.toString() != binding.password.text.toString() -> {
                binding.confirmPasswordTextInputLayout.error = "Passwords don't match"
                binding.confirmPassword.requestFocus()
                return false
            }
            else -> {
                binding.confirmPasswordTextInputLayout.isErrorEnabled = false
            }
        }
        return true
    }

    /**
     * applying text watcher on each text field
     */
    inner class TextFieldValidation(private val view: View) : TextWatcher {
        override fun afterTextChanged(s: Editable?) {}
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            // checking ids of each text field and applying functions accordingly.
            when (view.id) {
                R.id.userName -> {
                    validateUserName()
                }
                R.id.email -> {
                    validateEmail()
                }
                R.id.password -> {
                    validatePassword()
                }
                R.id.confirmPassword -> {
                    validateConfirmPassword()
                }
            }
        }
    }
}

Запустим приложение и полюбуемся поведением формы ввода:

Полный исходный код этого проекта можно скачать по ссылке ниже:

https://github.com/Mustufa786/TextInputLayout-FormValidation

Надеюсь, вы узнали из этой статьи что-то новое для себя. Следите за появлением новых статей! Успехов в разработке!

Узнать подробнее о курсе.

Introduction

TextInputLayout was introduced to display the floating label on EditText. The EditText has to be wrapped by TextInputLayout in order to display the floating label.

TextInputLayout is a layout which wraps an EditText (or descendant) to show a floating label when the hint is hidden due to the user inputting text. Additonally the TextInputLayout enables you to display an error message below the EditText.

Make sure the following dependency is added to your app’s build.gradle file under dependencies:

compile 'com.android.support:design:25.3.1'

Basic usage

It is the basic usage of the TextInputLayout.
Make sure to add the dependency in the build.gradle file as described in the remarks section.

Example:

 <android.support.design.widget.TextInputLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content">

     <EditText
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:hint="@string/username"/>
 
 </android.support.design.widget.TextInputLayout>

Handling Errors

You can use the TextInputLayout to display error messages according to the material design guidelines using the setError and setErrorEnabledmethods.

In order to show the error below the EditText use:

TextInputLayout til = (TextInputLayout) findViewById(R.id.username);
til.setErrorEnabled(true);
til.setError("You need to enter a name");

To enable error in the TextInputLayout you can eithr use app:errorEnabled="true" in xml or til.setErrorEnabled(true); as shown above.

You will obtain:

enter image description here

Adding Character Counting

The TextInputLayout has a character counter for an EditText defined within it.
The counter will be rendered below the EditText.

Just use the setCounterEnabled() and setCounterMaxLength methods:

TextInputLayout til = (TextInputLayout) findViewById(R.id.username);
til.setCounterEnabled(true);
til.setCounterMaxLength(15);

or the app:counterEnabled and app:counterMaxLength attributes in the xml.

<android.support.design.widget.TextInputLayout
    app:counterEnabled="true"
    app:counterMaxLength="15">

    <EditText/>

</android.support.design.widget.TextInputLayout>

Password Visibility Toggles

With an input password type, you can also enable an icon that can show or hide the entire text using the passwordToggleEnabled attribute.

You can also customize same default using these attributes:

  • passwordToggleDrawable: to change the default eye icon
  • passwordToggleTint: to apply a tint to the password visibility toggle drawable.
  • passwordToggleTintMode: to specify the blending mode used to apply the background tint.

Example:

<android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:passwordToggleContentDescription="@string/description"
        app:passwordToggleDrawable="@drawable/another_toggle_drawable"
        app:passwordToggleEnabled="true">

            <EditText/>

</android.support.design.widget.TextInputLayout>

TextInputEditText

The TextInputEditText is an EditText with an extra fix to display a hint in the IME when in ‘extract’ mode.

The Extract mode is the mode that the keyboard editor switches to when you click on an EditText when the space is too small (for example landscape on a smartphone).
In this case, using an EditText while you are editing the text you can see that the IME doesn’t give you a hint of what you’re editing

The TextInputEditText fixes this issue providing hint text while the user’s device’s IME is in Extract mode.

Example:

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Description"
    >
    <android.support.design.widget.TextInputEditText
        android:id="@+id/description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</android.support.design.widget.TextInputLayout>

Customizing the appearance of the TextInputLayout

You can customize the appearance of the TextInputLayout and its embedded EditTextby defining custom styles in your styles.xml. The defined styles can either be added as styles or themes to your TextInputLayout.

Example for customizing the hint appearance:

styles.xml:

<!--Floating label text style-->  
<style name="MyHintStyle" parent="TextAppearance.AppCompat.Small">  
    <item name="android:textColor">@color/black</item>
</style>

<!--Input field style-->  
<style name="MyEditText" parent="Theme.AppCompat.Light">  
    <item name="colorControlNormal">@color/indigo</item>
    <item name="colorControlActivated">@color/pink</item>
</style>  

To Apply Style update your TextInputLayout And EditText as follows

<android.support.design.widget.TextInputLayout  
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:hintTextAppearance="@style/MyHintStyle">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/Title"
        android:theme="@style/MyEditText" />

</android.support.design.widget.TextInputLayout>  

Example to customize the accent color of the TextInputLayout. The accent color affects the color of the baseline of the EditText and the text color for the floating hint text:

styles.xml:

<style name="TextInputLayoutWithPrimaryColor" parent="Widget.Design.TextInputLayout">
    <item name="colorAccent">@color/primary</item>
</style> 

layout file:

<android.support.design.widget.TextInputLayout
            android:id="@+id/textInputLayout_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/TextInputLayoutWithPrimaryColor">

            <android.support.design.widget.TextInputEditText
                android:id="@+id/textInputEditText_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/login_hint_password"
                android:inputType="textPassword" />

</android.support.design.widget.TextInputLayout>

Вступление

TextInputLayout был введен для отображения плавающей метки в EditText. EditText должен быть обернут TextInputLayout для отображения плавающей метки.

замечания

TextInputLayout — это макет, который обертывает EditText (или потомок), чтобы показать плавающий метку, когда подсказка скрыта из-за ввода пользователем текста. Кроме того, TextInputLayout позволяет отображать сообщение об ошибке ниже EditText .

Убедитесь, что в файл build.gradle вашего приложения добавлена build.gradle зависимость:

compile 'com.android.support:design:25.3.1'

Основное использование

Это основное использование TextInputLayout .
Обязательно добавьте зависимость в файле build.gradle как описано в разделе примечаний.

Пример:

 <android.support.design.widget.TextInputLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content">

     <EditText
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:hint="@string/username"/>
 
 </android.support.design.widget.TextInputLayout>

Обработка ошибок

Вы можете использовать TextInputLayout для отображения сообщений об ошибках в соответствии с рекомендациями по разработке материалов с использованием методов setError и setErrorEnabled .

Чтобы показать ошибку ниже использования EditText:

TextInputLayout til = (TextInputLayout) findViewById(R.id.username);
til.setErrorEnabled(true);
til.setError("You need to enter a name");

Чтобы включить ошибку в TextInputLayout вы можете использовать app:errorEnabled="true" в xml или til.setErrorEnabled(true); как показано выше.

Вы получите:

введите описание изображения здесь

Добавление подсчета символов

TextInputLayout имеет счетчик символов для EditText, определенных внутри него.
Счетчик будет отображаться ниже EditText.

Просто используйте setCounterEnabled() и setCounterMaxLength :

TextInputLayout til = (TextInputLayout) findViewById(R.id.username);
til.setCounterEnabled(true);
til.setCounterMaxLength(15);

или app:counterEnabled и app:counterMaxLength в xml.

<android.support.design.widget.TextInputLayout
    app:counterEnabled="true"
    app:counterMaxLength="15">

    <EditText/>

</android.support.design.widget.TextInputLayout>

Переключатели просмотров

С типом ввода пароля вы также можете включить значок, который может отображать или скрывать весь текст, используя атрибут passwordToggleEnabled .

Вы также можете настроить тот же параметр по умолчанию, используя следующие атрибуты:

  • passwordToggleDrawable : изменить значок глаз по умолчанию
  • passwordToggleTint : применить оттенок к видимости пароля для переключения.
  • passwordToggleTintMode : указать режим наложения, используемый для применения фонового оттенка.

Пример:

<android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:passwordToggleContentDescription="@string/description"
        app:passwordToggleDrawable="@drawable/another_toggle_drawable"
        app:passwordToggleEnabled="true">

            <EditText/>

</android.support.design.widget.TextInputLayout>

TextInputEditText

TextInputEditText — это EditText с дополнительным исправлением, чтобы отображать подсказку в IME в режиме «extract» .

Режим Extract — это режим, который переключает редактор клавиатуры, когда вы нажимаете EditText, когда пространство слишком мало (например, пейзаж на смартфоне).
В этом случае, используя EditText во время редактирования текста, вы можете видеть, что IME не дает вам намека на то, что вы редактируете

TextInputEditText исправляет эту проблему, предоставляя текст подсказки, в то время как IME устройства пользователя находится в режиме Extract.

Пример:

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Description"
    >
    <android.support.design.widget.TextInputEditText
        android:id="@+id/description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</android.support.design.widget.TextInputLayout>

Вы можете настроить внешний вид TextInputLayout и встроенного EditText , указав пользовательские стили в ваших styles.xml . Определенные стили могут быть добавлены как стили или темы в TextInputLayout .

Пример настройки отображения подсказки:

styles.xml :

<!--Floating label text style-->  
<style name="MyHintStyle" parent="TextAppearance.AppCompat.Small">  
    <item name="android:textColor">@color/black</item>
</style>

<!--Input field style-->  
<style name="MyEditText" parent="Theme.AppCompat.Light">  
    <item name="colorControlNormal">@color/indigo</item>
    <item name="colorControlActivated">@color/pink</item>
</style>  

Чтобы применить стиль, обновите TextInputLayout и EditText следующим образом.

<android.support.design.widget.TextInputLayout  
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:hintTextAppearance="@style/MyHintStyle">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/Title"
        android:theme="@style/MyEditText" />

</android.support.design.widget.TextInputLayout>  

Пример настройки цвета акцента TextInputLayout . Цвет акцента влияет на цвет базовой линии EditText и цвет текста для плавающего текста подсказки:

styles.xml :

<style name="TextInputLayoutWithPrimaryColor" parent="Widget.Design.TextInputLayout">
    <item name="colorAccent">@color/primary</item>
</style> 

файл макета:

<android.support.design.widget.TextInputLayout
            android:id="@+id/textInputLayout_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/TextInputLayoutWithPrimaryColor">

            <android.support.design.widget.TextInputEditText
                android:id="@+id/textInputEditText_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/login_hint_password"
                android:inputType="textPassword" />

</android.support.design.widget.TextInputLayout>

TextInputLayout is the currently accepted, first party text input widget intended to match material design specs. However the widget itself does not lend itself to be easily styled to fit your needs, and documentation is sparse.

This tutorial comes from my own personal solution to theming and styling a TextInputLayout. It goes beyond just the regular theme colors but also ensuring all components of the layout (hint, label, input, error) have text appearances that you have control over.

Basic TextInputLayout

Your basic TextInputLayout starts out like this:

<android.support.design.widget.TextInputLayout
    android:id="@+id/text_input_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Field 1">
    
  <android.support.design.widget.TextInputEditText
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/>
      
</android.support.design.widget.TextInputLayout>

And basic theme colors are set like this:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  <!-- Customize your theme here. -->
  <item name="colorPrimary">#3F51B5</item> // (Indigo 500)
  <item name="colorPrimaryDark">#303F9F</item> // (Indigo 700)
  <item name="colorAccent">#00897B</item> // (Teal 600)
</style>

And we get this result:

basic TextInputLayout style

Noteworthy observations:

  • Only colorAccent is used from the basic app theme colors.
  • We’ve applied a custom font via Calligraphy (Fira Sans) for effect. We see here that the hint, label, and input text are properly formatted, but error text is Roboto.

Even more annoying is when an error layout is selected, the label highlight still uses colorAccent which results in this awkward looking UI — a mix of colorAccent and red error, which may or may not clash:

what is this

Colors

Start with defining colors you want to apply:

<color name="error_color">#C62828</color> // Error color (Red 800)
<color name="hint_color_active">#00897B</color> // Active label color (Teal 600)
<color name="hint_color_inactive">#9E9E9E</color> // Inactive label / Hint (Grey 500)

And set up styles and themes like so:

<style name="MyStyle.InputLayout" parent="Widget.Design.TextInputLayout">
  <item name="fontPath">@string/app_font</item>
  <item name="errorTextAppearance">@style/ErrorTextAppearance</item>
  <item name="hintTextAppearance">@style/HintTextAppearance</item>
</style>

<style name="MyTheme.EditText" parent="Theme.AppCompat.Light">
  <!-- Inactive underline color-->
  <item name="colorControlNormal">@color/hint_color_inactive</item>
  
  <!-- Cursor and Active underline color, uses colorAccent by default if not defined-->
  <item name="colorControlActivated">@color/hint_color_active</item>
</style>

<style name="HintTextAppearance" parent="TextAppearance.Design.Hint">
  <!-- Inactive and Active label color, pointing to a selector-->
  <item name="android:textColor">@color/hint_color</item>
</style>

<style name="ErrorTextAppearance" parent="TextAppearance.Design.Error">
  <!-- Error text color-->
  <item name="android:textColor">@color/error_color</item>
</style>

// res/color/hint_color.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_focused="true" android:color="@color/hint_color_active"/>
  <item android:color="@color/hint_color_inactive"/>
</selector>

And applying them to your views:

<android.support.design.widget.TextInputLayout
    android:id="@+id/text_input_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Field 1"
    style="@style/MyStyle.InputLayout">
    
  <android.support.design.widget.TextInputEditText
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/MyTheme.EditText"/>
      
</android.support.design.widget.TextInputLayout>

What we can’t accomplish with pure styling alone is:

  • Error label color
  • Error view typeface
  • Error cursor color

new-style-error-select

The Error View

The way TextInputLayout manages its error view thwarts how Calligraphy works. Essentially, Calligraphy only works when inflating views that have a fontPath attribute applied — it can’t apply a font path to a programmatically created view, even if the style with a fontPath is applied programmatically. Because the error view is created programmatically (and not inflated), any fontPath provided in errorTextAppearance is simply ignored.

Another annoying caveat is the exposed setTypeface method, which is intended to allow specifying the error and hint typefaces:

// TextInputLayout
/**
 * Set the typeface to use for the hint and any label views (such as counter and error views).
 *
 * @param typeface typeface to use, or {@code null} to use the default.
 */
public void setTypeface(@Nullable Typeface typeface);

but will fail to apply the typeface to the error view if it’s invoked before setErrorEnabled(true):

public void setTypeface(@Nullable Typeface typeface) {
  ...
  
  // this is null by default until the first time setErrorEnabled(true) is invoked
  if (mErrorView != null) { 
    mErrorView.setTypeface(typeface);
  }
}

So my recommendation is to enable setErrorEnabled in TextInputLayout styles by default:

<style name="MyStyle.InputLayout" parent="Widget.Design.TextInputLayout">
  <item name="errorEnabled">true</item>
</style>

Error View: Typeface

Android 8.0 (API 26) introduced Fonts in XML and you’re encouraged to explore that solution to see if that works for you. I’ll walk through the more lengthy, Calligraphy based solution below.

Create a custom TextInputLayout, and load and apply setTypeface in the constructor using whatever fontPath is applied to the entire layout:

private static final int[] FONT_PATH = new int[] { R.attr.fontPath };

public MyTextInputLayout(...) {
    ...
    Typeface typeface = TypefaceUtils.load(context.getAssets(),
        loadFontPathFromStyle(context, attrs, FONT_PATH));

    // Only works if errorEnabled == true. Either call it here or set it in your style.
    setTypeface(typeface);
  }

  // Implementation from CalligraphyUtils#pullFontPathFromStyle
  private String loadFontPathFromStyle(Context context, AttributeSet attrs, int[] attributeId) {
    if (attributeId == null || attrs == null) return null;
    final TypedArray typedArray = context.obtainStyledAttributes(attrs, attributeId);
    if (typedArray != null) {
      try {
        // First defined attribute
        String fontFromAttribute = typedArray.getString(0);
        if (!TextUtils.isEmpty(fontFromAttribute)) {
          return fontFromAttribute;
        }
      } catch (Exception ignore) {
        // Failed for some reason.
      } finally {
        typedArray.recycle();
      }
    }
    return null;
  }

Remember this only works if errorEnabled is set to true in the style; otherwise you need override setErrorEnabled and call setTypeface yourself.

Label View: Error Active Color

We will create a custom attribute to represent the error state for our TextInputLayout implementation:

// attrs.xml
<resources>
  <declare-styleable name="ErrorState">
    <attr format="boolean" name="state_error"/>
  </declare-styleable>
</resources>
public class MyTextInputLayout extends TextInputLayout {

  private static final int[] ERROR_STATE = new int[] { R.attr.state_error };

  private boolean errorState = false;

  ...
  
  @Override
  public void setError(@Nullable CharSequence error) {
    // We'll manage the view's error state by calls to this method, which correctly reflects when the TextInputLayout hides/shows the error text
    errorState = !TextUtils.isEmpty(error);
    refreshDrawableState();
    super.setError(error);
  }
  
  @Override
  protected int[] onCreateDrawableState(int extraSpace) {
    // add a new drawable state we are defining as error state
    int[] state = super.onCreateDrawableState(extraSpace + 1);
    if (errorState) {
      View.mergeDrawableStates(state, ERROR_STATE);
    }
    return state;
  }  
}

We update the hint text color referenced in the style for hintTextAppearance to take advantage of this new state:

// res/color/hint_color.xml
<selector xmlns:app="http://schemas.android.com/apk/res-auto">
  <item app:state_error="true" android:color="@color/error_color"/>
  ...
</selector>

Add everything up and we should be able to reproduce this result, which has the correct error text view font applied, custom error label text color, custom hint and underline colors:

new-style

And selected error view behavior:

new-style-error-select

Error Cursor Color?

Have colorControlActivated also point to a selector that takes advantage of the new error state we’ve created, similar to hint_color.

Summary

Style/theme attributes can get you most places when styling the TextInputLayout, but fixing the error text typeface and tweaking the error label color takes more effort to deliver a look and feel that you want for your app outside of stock behavior.

Понравилась статья? Поделить с друзьями:
  • Test completed successfully without error punkbuster
  • Test completed successfully without error battlefield
  • Temporary server error prx2
  • Test c 1 10 fatal error freeimage h no such file or directory
  • Temporary server error please try again later prx5