TextInputLayout の Tips 5 選

# TextInputLayout とは

[TextInputLayout](http://developer.android.com/intl/ja/reference/android/support/design/widget/TextInputLayout.html) は [Android Design Support Library](http://android-developers.blogspot.jp/2015/05/android-design-support-library.html) に含まれている、テキスト入力機能を提供する View です。[マテリアルデザインの Text Fields](https://www.google.com/design/spec/components/text-fields.html#text-fields-single-line-text-field) によく従っているので、マテリアルデザインを実現したいときに重宝します。

![](https://material-design.storage.googleapis.com/publish/material_v_4/material_ext_publish/0Bzhp5Z4wHba3Z2tnU1NHUkg1NWc/components_textfields_single4.png)
([Text fields - Components - Google design guidelines](https://www.google.com/design/spec/components/text-fields.html#text-fields-single-line-text-field) より)

しかしながら、最新の [v23.1.1](http://developer.android.com/intl/ja/tools/support-library/index.html#revisions) においても、知らないとハマってしまう箇所がいくつかあります。

この記事では、TextInputLayout を使う際に気を付けた方が良い点について解説します。

# Tips

## 1. 子供の `EditText` にも id を振った方が良い
`TextInputLayout` は以下のように、`EditText` を入れ子にして使います。

```xml:activity_main.xml
    <android.support.design.widget.TextInputLayout
        android:id="@+id/text_input_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/message"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/some_hints"
            android:inputType="text"
            android:minHeight="@dimen/text_caption"
            android:textSize="@dimen/text_caption" />

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

このとき、 [TextInputLayout#getEditText()](http://developer.android.com/intl/ja/reference/android/support/design/widget/TextInputLayout.html#getEditText()) で、子供の `EditText` が取得できるので、わざわざ `EditText` に id を振る必要はないように見えます。そして、実際、なくても動くように見えます。

しかし、画面を回転させてみましょう。入力途中のテキストが消えるはずです。

これは何故かというと、Android SDK が自動的に View の状態を保存/復元するときに、View が ID を持っているかどうかが判断の基準になるからです。[Android Developers](http://developer.android.com/intl/ja/training/basics/activity-lifecycle/recreating.html) にも、以下のようにしっかり書かれています。

> **注**: Android システムがアクティビティのビューの状態を復元できるようにするためには、**各ビューは固有の ID を持っている必要があります**。これは [android:id](http://developer.android.com/reference/android/view/View.html#attr_android:id) によって提供されます。

([アクティビティを再作成する | Android Developers](http://developer.android.com/intl/ja/training/basics/activity-lifecycle/recreating.html) より)

ということで、使わなくても、内部の `EditText` には `android:id` を指定しておきましょう。

余談ですが、この ID の有無をチェックしているコードは [https://github.com/android/platform_frameworks_base/blob/android-6.0.0_r7/core/java/android/view/View.java#L14740](https://github.com/android/platform_frameworks_base/blob/android-6.0.0_r7/core/java/android/view/View.java#L14740) にあります。筆者は、デバッグ実行して、ここに辿りついて、ようやく上記の仕様を思い出しました(Lint で Warning を出してほしい)。

## 2. ヒントをアニメーションさせたくない時は
`TextInputLayout` は、忠実にマテリアルデザインの [floating labels](http://www.google.com/design/spec/components/text-fields.html#text-fields-labels) を実現するために、テキストボックスに入力を始めると、ヒントがキャプションにふわっと移動する、という Android にしては珍しく気の利いた機能が実装されています。

<img alt="screencast 2015-12-31 07-56-36.gif" src="https://qiita-image-store.s3.amazonaws.com/0/36306/80b20c8a-a600-fd2d-b6a4-b0f4d61cd9f6.gif" width="50%"/>

しかし、たまに邪魔です。

このアニメーションをオフにするメソッドは提供されていませんが、以下のようにプログラマティックに子供の `EditText` にヒントを指定すれば実現できます。ちなみに、これに関しては [AOSP に Issue も開かれています](https://code.google.com/p/android/issues/detail?id=181590&q=TextInputLayout&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars)。

```java:MainActivity.java
    final TextInputLayout til = (TextInputLayout) findViewById(R.id.text2);
    final CharSequence hint = til.getHint();
    til.setHint(null);
    til.getEditText().setHint(hint);
```

<img alt="screencast 2015-12-31 08-08-02.gif" src="https://qiita-image-store.s3.amazonaws.com/0/36306/b982101a-bb43-7b74-863d-0baa03a8a7dc.gif" width="50%"/>

## 3. `TextInputLayout` と baseline を合わせるには

**注意!** Support Library 24.2.0 から、 `TextInputLayout` は `LinearLayout` を継承しないため、ここで書いてある Tip は使えないようです(参照: https://code.google.com/p/android/issues/detail?id=223772)。
@litmon さん、[情報](http://qiita.com/hkurokawa/items/baf0dca930a32a5a7f92#comment-55113c64bbdab90e71f5)ありがとうございました。

*下記の内容は参考までに残しておきますが、Support Library 24.2.0 以降では動かないことにご注意ください*

通常、`EditText` や `TextView` を水平方向に並べるときは `RelativeLayout` の [android:layout_alignBaseline](http://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html#attr_android:layout_alignBaseline) 属性などを使うことで、テキストのベースラインが揃うようにします。

しかし、`TextInputLayout` が絡むと、簡単にはいきません。なぜなら、親の `TextInputLayout` に `android:layout_alignBaseline` 属性を指定しても、子供の `EditText` のベースラインには効かないからです :cry: 

そういうときは以下のように指定します。`TextInputLayout` は `LinearLayout` を継承しているので、この [android:baselineAlignedChildIndex](http://developer.android.com/reference/android/widget/LinearLayout.html#attr_android:baselineAlignedChildIndex) 属性が使えるのです。

```xml:activity_main.xml
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:text="パイント" />

    <android.support.design.widget.TextInputLayout
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:baselineAlignedChildIndex="0"
        android:layout_alignBaseline="@id/label"
        android:layout_toLeftOf="@id/label">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="飲んだビールの量" />
    </android.support.design.widget.TextInputLayout>
```

## 4. カウンターやエラーメッセージの見た目を変更するには
カウンターやエラーメッセージのスタイルは以下のように変更することができます。

```xml:activity_main.xml
    <android.support.design.widget.TextInputLayout
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:counterEnabled="true"
        app:counterMaxLength="10"
        app:counterTextAppearance="@style/counterText"
        app:counterOverflowTextAppearance="@style/counterOverflowText"
        app:errorTextAppearance="@style/errorText">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </android.support.design.widget.TextInputLayout>
```

```xml:values/styles.xml
    <style name="counterText">
        <item name="android:textColor">@android:color/holo_green_dark</item>
    </style>

    <style name="counterOverflowText">
        <item name="android:textStyle">bold</item>
    </style>

    <style name="errorText">
        <item name="android:textColor">@android:color/holo_red_dark</item>
        <item name="android:textStyle">bold</item>
    </style>
```

なお、[TextInputLayout](http://developer.android.com/reference/android/support/design/widget/TextInputLayout.html) の実装を読むと、エラーテキストとカウンターは以下のような `LinearLayout` で表される `mIndicatorArea` の子要素として動的に追加されるようです(**注：**生成される View がどういうものか表すための概念としての XML です。実際にこの XML が使われるわけではありません)。

```xml
    <LinearLayout
        android:id="@+id/indicatorArea"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/errorView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <Space
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/counterView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:maxLines="1"/>
    </LinearLayout>
```

互いの表示/非表示に関わらず、エラーメッセージが左寄せ、カウンターが右寄せになることが分かります。

## 5. エラーメッセージ非表示時に View の高さが戻らない問題

複数行にまたがるエラーメッセージを `setError(CharSequence)` で表示したあとに `setError(null)` を呼ぶと、エラーメッセージの View の visibility が `View.INVISIBLE` になりますが、 View 自体は削除されないため、下部に無駄なスペースが残ってしまいます。

これを元に戻すには `setErrorEnabled(false)` を呼びます。これによってエラーメッセージの View の visibility が `View.GONE` になります。

```java:MainActivity.java
        final TextInputLayout til = (TextInputLayout) findViewById(R.id.text2);
        til.getEditText().addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(Editable s) {
                if (s.toString().contains("error")) {
                    til.setError("WRYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY!");
                } else {
                    til.setErrorEnabled(false);
                }
            }
```

# おわりに
Design Support Library で提供される [TextInputLayout](http://developer.android.com/intl/ja/reference/android/support/design/widget/TextInputLayout.html) を使う際に注意する点や tips を紹介しました。

ググると分かりますが、サポートライブラリのバージョンによって挙動が異なるようです。たとえば、[Working with the EditText · codepath/android_guides Wiki](https://github.com/codepath/android_guides/wiki/Working-with-the-EditText) ではカウンタの表示が左寄せですが、最新の [v23.1.1](http://developer.android.com/intl/ja/tools/support-library/index.html#revisions) では右寄せになっています。使う際には、そのあたりにも注意が必要になるかもしれません。

# 参考
- [TextInputLayout | Android Developers](http://developer.android.com/intl/ja/reference/android/support/design/widget/TextInputLayout.html)
- [Working with the EditText · codepath/android_guides Wiki](https://github.com/codepath/android_guides/wiki/Working-with-the-EditText)
- [TextInputLayout の Tips - 質量](http://teshi04.hatenablog.com/entry/2015/11/18/191654)