Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Макет 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;
}
}
}
Текст ошибки выводится снизу от текстового поля.
Стиль для сообщения об ошибке можно стилизовать. Добавим новый атрибут.
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>
Теперь выводится другим цветом.
Расширенный вид стилей:
<!--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>
Когда будет превышен лимит, то цвет счётчика изменится. Этот цвет можно стилизовать через стиль.
<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.
Разметка экрана.
<?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 и получаем выпадающий список из массива строк.
Мы получили рабочий прототип, но при повороте меню работать не будет. Проверьте самостоятельно. Поэтому немного поправим код.
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">
Казалось бы простой компонент, никаких трудностей не возникает. Но не торопитесь. Стоит повернуть устройство в альбомный режим, как текстовое поле растянется на весь экран и никакой подсказки вы не увидите. Возможно, это баг, который когда-нибудь починят. Но проблему легко решить, если вместо стандартного 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>