详解Android TextInputLayout:实用示例与开发技巧

在本教程中,我们将深入了解Android TextInputLayout提供的功能。Android TextInputLayout是一个带有Material Design支持库的设计组件。

安卓的文本输入布局

Android的TextInputLayout继承自LinearLayout。TextInputLayout的主要用途是作为EditText(或其子类)的包装器,并启用浮动提示动画。经验法则:TextInputLayout应该包装TextInputEditText而不是普通的EditText。原因是?TextInputEditText是EditText的子类,专门设计用作TextInputLayout的子控件。此外,使用EditText会发出警告:添加的EditText不是TextInputEditText,请改用该类。TextInputLayout不仅可以显示浮动提示标签,还提供了更多功能。

安卓的TextInputLayout功能

在这个教程中,我们将会介绍一些功能:

  • 启用/禁用浮动提示
  • 启用/禁用浮动提示动画
  • 显示错误消息
  • 显示字符计数器
  • 当字符计数超过限制时提醒用户
  • 自定义浮动提示、错误标签、字符计数器的文本外观
  • 密码可见性切换

我们将会逐一研究这些特点,并在Android Studio项目中实现它们。

Android TextInputLayout示例项目结构

这是一个单Activity应用程序。我们将在layout、activity和styles.xml以及colors.xml文件中完成所有的工作。首先,按照下面所示,在build.gradle文件中添加关于design support library的依赖。

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

启用/禁用悬浮提示

在TextInputLayout中,默认情况下启用了浮动提示。要禁用它,我们需要在标签内添加以下属性:app:hintEnabled=”false”。下面的xml代码来自activity_main.xml布局,包含三个EditText字段。

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.Olivia.featuresoftextinputlayout.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">


        <android.support.design.widget.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            android:hint="TextInputEditText" />


        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="@dimen/activity_horizontal_margin">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Floating Hint Enabled Default" />

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


        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:hintEnabled="false">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Floating Hint Disabled" />

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

</LinearLayout>
</ScrollView>

第三个EditText字段的浮动提示被禁用。让我们看看上述代码的输出结果:

启用/禁用浮动提示动画

这是文章《Android的TextInputLayout示例》的第2部分(共5部分)。

与之前的特性类似,TextInputLayout默认情况下启用了浮动提示动画。要禁用此动画,我们需要在TextInputLayout标签中添加以下属性:app:hintAnimationEnabled=”false”。下面的XML代码来自activity_main.xml布局文件,其中展示了两种情况下的EditText字段实现。

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.Olivia.featuresoftextinputlayout.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="@dimen/activity_horizontal_margin">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="默认启用浮动提示" />

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


        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:hintAnimationEnabled="false">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="禁用提示动画" />

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

</LinearLayout>
</ScrollView>

以上代码的输出效果如下所示。值得注意的是,当第二个EditText字段获得焦点时,其浮动提示将不会显示动画效果。

设置提示文本的外观

要为提示文本使用自定义的颜色和大小,可以使用以下属性:app:hintTextAppearance=”@style/HintText”。在styles.xml文件中,可以按照下面的示例定义HintText样式:

<style name="HintText" parent="TextAppearance.Design.Hint">
        <item name="android:textSize">16sp</item>
        <item name="android:textColor">@color/colorPrimary</item>
    </style>

下面的XML代码来自activity_main.xml布局文件,展示了两种情况(使用和未使用hintTextAppearance属性)的EditText字段实现。

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.Olivia.featuresoftextinputlayout.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="@dimen/activity_horizontal_margin">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="启用浮动提示" />

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


        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:hintTextAppearance="@style/HintText">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="自定义提示文本外观" />

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

</LinearLayout>
</ScrollView>

上述代码的输出效果如下所示。

字符计数器

字符计数器是许多应用程序常用的功能(例如Twitter的字符限制)。通过将app:counterEnabled设置为true,并使用app:counterMaxLength属性,可以设置TextInputLayout中的最大字符数。默认情况下,字符计数器显示在EditText的右下角位置,在编写本教程时,尚无改变其位置的方法。设置计数器样式的方式与设置提示文本样式类似,这里使用的属性是app:counterTextAppearance。我们在项目的styles.xml文件中添加了以下样式。

<style name="CounterText" parent="TextAppearance.Design.Counter">
        <item name="android:textSize">16sp</item>
        <item name="android:textColor">@color/my_pink</item>
    </style>

以下XML代码来自activity_main.xml布局文件,展示了包含默认字符计数器和自定义字符计数器的EditText字段实现。

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.Olivia.featuresoftextinputlayout.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:counterEnabled="true"
            app:counterMaxLength="5"
            app:hintTextAppearance="@style/HintText">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="字符计数器限制为10" />

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


        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:counterEnabled="true"
            app:counterMaxLength="5"
            app:counterTextAppearance="@style/CounterText"
            app:hintTextAppearance="@style/HintText">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="自定义文本外观的字符计数器" />

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

</LinearLayout>
</ScrollView>

以上代码的输出效果如下。让我们仔细观察这些输出结果:

  • 第一个EditText字段在字符计数超过限制时,会改变计数器的文本颜色、提示文本颜色和指示器颜色。
  • 第二个EditText字段在超过限制时也会执行相同操作,此外还会改变计数器的自定义文本颜色和自定义文本大小。

当字符计数超过限制时,若要指定我们想要的样式,需要使用接下来将要介绍的counterOverflow属性。

字符计数器溢出

正如我们在上面所看到的,当字符数超过定义的限制时,计数器文本会使用在counterOverflow中定义的属性。如果该属性不存在,它将使用默认属性,正如我们在上面的输出中所看到的那样。我们需要使用以下参数:app:counterOverflowTextAppearance。CounterOverflow的样式在styles.xml文件中定义。

 <style name="CounterOverFlow" parent="TextAppearance.Design.Counter.Overflow">
        <item name="android:textSize">16sp</item>
        <item name="android:textColor">@color/my_orange</item>
    </style>

将以下代码片段添加到之前的activity_main.xml布局文件中:

<android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:counterEnabled="true"
            app:counterMaxLength="5"
            app:counterOverflowTextAppearance="@style/CounterOverFlow"
            app:counterTextAppearance="@style/CounterText"
            app:hintTextAppearance="@style/HintText">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="自定义文本外观的计数器溢出" />

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

让我们再次运行应用程序查看效果。

错误标签

将app:errorEnabled设置为true可以使我们在EditText字段下方显示一个错误文本。要样式化错误文本,我们可以使用属性app:errorTextAppearance,并在styles.xml文件中添加以下代码。

<style name="ErrorText" parent="TextAppearance.Design.Error">
        <item name="android:textSize">16sp</item>
        <item name="android:textColor">@color/my_black</item>
    </style>

以下的XML代码来自activity_main.xml布局,其中包含了一个默认错误标签和一个自定义错误标签的EditText字段。

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.Olivia.featuresoftextinputlayout.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:id="@+id/errorInputLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:counterEnabled="true"
            app:counterMaxLength="5"
            app:counterOverflowTextAppearance="@style/CounterOverFlow"
            app:counterTextAppearance="@style/CounterText"
            app:errorEnabled="true"
            app:hintTextAppearance="@style/HintText">

            <android.support.design.widget.TextInputEditText
                android:id="@+id/errorEditText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Default Error Label" />

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


        <android.support.design.widget.TextInputLayout
            android:id="@+id/customErrorInputLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:counterEnabled="true"
            app:counterMaxLength="5"
            app:counterOverflowTextAppearance="@style/CounterOverFlow"
            app:counterTextAppearance="@style/CounterText"
            app:errorEnabled="true"
            app:errorTextAppearance="@style/ErrorText"
            app:hintTextAppearance="@style/HintText">

            <android.support.design.widget.TextInputEditText
                android:id="@+id/customErrorEditText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Custom Error Label" />

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

</LinearLayout>
</ScrollView>

要显示错误文本,我们需要在MainActivity.java类中调用TextInputLayout实例的setError(String)方法,如下所示。

package com.Olivia.featuresoftextinputlayout;

import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;

public class MainActivity extends AppCompatActivity {


    TextInputLayout errorInputLayout, customErrorInputLayout;
    TextInputEditText errorEditText, customErrorEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        errorEditText = (TextInputEditText) findViewById(R.id.errorEditText);
        errorInputLayout = (TextInputLayout) findViewById(R.id.errorInputLayout);

        customErrorEditText = (TextInputEditText) findViewById(R.id.customErrorEditText);
        customErrorInputLayout = (TextInputLayout) findViewById(R.id.customErrorInputLayout);

        errorEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {

                if (s.length() > errorInputLayout.getCounterMaxLength())
                    errorInputLayout.setError("Max character length is " + errorInputLayout.getCounterMaxLength());
                else
                    errorInputLayout.setError(null);

            }
        });

        customErrorEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {

                if (s.length() > customErrorInputLayout.getCounterMaxLength())
                    customErrorInputLayout.setError("Max character length is " + customErrorInputLayout.getCounterMaxLength());
                else
                    customErrorInputLayout.setError(null);

            }
        });


    }
}

在以上代码中,我们在每个TextInputEditText实例上添加了一个实现了TextWatcher接口的TextChangedListener。当输入的字符计数超过计数器的最大限制时,我们会显示错误标签。为了清除错误标签,我们将setError()方法中的值设置为null。以上代码的输出效果如下:

注意:文本字段的指示器使用与错误标签相同的颜色。它会覆盖由counterOverflow设置的颜色,因此拥有最高的优先级。

密码可见性切换

将app:passwordToggleEnabled属性设置为true可以启用密码显示/隐藏功能。若要更改切换图标颜色,可以使用app:passwordToggleTint属性。下面的XML代码来自activity_main.xml布局文件,其中展示了用于密码可见性切换的EditText字段(包括默认图标和自定义色调的示例)。

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.Olivia.featuresoftextinputlayout.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:counterEnabled="true"
            app:counterMaxLength="5"
            app:counterOverflowTextAppearance="@style/CounterOverFlow"
            app:counterTextAppearance="@style/CounterText"
            app:hintTextAppearance="@style/HintText"
            app:passwordToggleEnabled="true">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="密码可见性切换"
                android:inputType="textPassword" />

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


        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:counterEnabled="true"
            app:counterMaxLength="5"
            app:counterOverflowTextAppearance="@style/CounterOverFlow"
            app:counterTextAppearance="@style/CounterText"
            app:hintTextAppearance="@style/HintText"
            app:passwordToggleEnabled="true"
            app:passwordToggleTint="@color/my_orange">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="密码可见性切换(自定义色调)"
                android:inputType="textPassword" />

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

以上代码的输出效果如下:请注意,我们可以使用app:passwordToggleDrawable属性来设置自定义图标,以替代默认的密码可见性切换图标。至此,本教程已全部结束。我们已经介绍了TextInputLayout中的所有主要功能。您可以通过下方链接下载完整的Android TextInputLayout示例项目,该项目包含了以上所有代码片段。

下载 Android TextInputLayout 项目

参考资料:安卓官方文档

bannerAds