読者です 読者をやめる 読者になる 読者になる

woshidan's blog

そんなことよりコードにダイブ。

TextInputLayoutでエラーメッセージの表示/非表示を無理矢理出してみた

Android EditText TextInputLayout

TextInputLayout | Android Developers

TextInputLayout というものワクテカするものがあるらしく書いてみましたが、 ちょっと残念な感じの事態に遭遇して、やや残念な感じに対処してみた記録です。

内容

  • とりあえず置いてみた
  • エラーメッセージを表示する
    • 2回目以降表示のバグがある
  • エラーメッセージのViewを手動で追加してみる

とりあえず置いてみた

一番単純な形で置いてみました。InputTextLayoutの中にEditTextを置く必要があるっぽいです。

あと、

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

が必要です。念のため。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:orientation="vertical"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView android:text="TextInputLayoutTest"
        android:textSize="30sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <android.support.design.widget.TextInputLayout
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        >
        <EditText
            android:id="@+id/userid"
            android:textSize="20sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="ユーザIDを入力"/>
    </android.support.design.widget.TextInputLayout>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="フォーカス外し用"
        />
</LinearLayout>

これだけでも動いて楽しい。

f:id:woshidan:20151108224546g:plain

エラーメッセージを表示する

バリデーションに失敗した場合など、setError(CharSequence error) メソッドでエラーメッセージが表示できるそうです。

ボタンを押したとき、EditTextの値が空白だったら空白ですよ、と表示させてみます。

// 追加部分のみ
mTextInputLayout = (TextInputLayout) findViewById(R.id.text_input_layout);

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(mTextInputLayout.getEditText().getText().length() == 0) {
            mTextInputLayout.setErrorEnabled(true);
            mTextInputLayout.setError("空白です");

        } else {
            mTextInputLayout.setErrorEnabled(false);
            mTextInputLayout.setError(null);
        }
    }
});
<!-- 一部省略 -->
    <android.support.design.widget.TextInputLayout
        android:id="@+id/text_input_layout"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        >
        <EditText
            android:id="@+id/userid"
            android:textSize="20sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="ユーザIDを入力"/>
    </android.support.design.widget.TextInputLayout>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="フォーカス外し用"
        />

    <Button
        android:id="@+id/button"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:textSize="30sp"
        android:text="Push!"
        />

一見動いているように見えるのですが...

f:id:woshidan:20151108235728g:plain

もうちょっとがちゃがちゃやって、2回目以降のバリデーションに失敗した場合...

f:id:woshidan:20151108235814g:plain

エラーメッセージが出て来ないじゃないですか。

f:id:woshidan:20151108235814g:plain

Issue 190355 - android - TextInputLayout setError() will not show an error after it is cleared - Android Open Source Project - Issue Tracker - Google Project Hosting

ここの問題の処理は下記なのですが、なんでおかしいのか、 手動でErrorViewの追加とか試してみたけど、あまり上手くも行かず..。

    public void setErrorEnabled(boolean enabled) {
        if (mErrorEnabled != enabled) {
            if (mErrorView != null) {
                ViewCompat.animate(mErrorView).cancel();
            }

            if (enabled) {
                mErrorView = new TextView(getContext());
                mErrorView.setTextAppearance(getContext(), mErrorTextAppearance);
                mErrorView.setVisibility(INVISIBLE);
                addView(mErrorView);

                if (mEditText != null) {
                    // Add some start/end padding to the error so that it matches the EditText
                    ViewCompat.setPaddingRelative(mErrorView, ViewCompat.getPaddingStart(mEditText),
                            0, ViewCompat.getPaddingEnd(mEditText), mEditText.getPaddingBottom());
                }
            } else {
                removeView(mErrorView);
                mErrorView = null;
            }
            mErrorEnabled = enabled;
        }
    }

エラーメッセージのViewを手動で追加してみる

ライブラリのViewが表示されないと悲しいので、何とか自分でも考えてみました。

ErrorEnabledをさわらなければよい、というのはあるのですが、Errorのsetterでどうしても触れてしまいます。

public void setError(@Nullable CharSequence error) {
    if (!mErrorEnabled) {
        if (TextUtils.isEmpty(error)) {
            // If error isn't enabled, and the error is empty, just return
            return;
        }
        // Else, we'll assume that they want to enable the error functionality
        setErrorEnabled(true);  // やーん
    }

もちろん、エラーメッセージが出る、出ないなら、ある程度は空白があるものとして、スペースができることを許容する、というのはありです。

ですが、あの、setErrorEnabledでセットしてくれる、フォームの下線のスタイル使えないかな、とか、考えた結果、

  • ユーザーに見せる前に、エラーメッセージのフォームがもう表示されないよう一回ON/OFFする
  • TextInputLayoutの子要素に空のLinearLayoutかRelativeLayoutを追加
  • エラーメッセージの欄は手動で上記の空のLayout要素に追加/削除する
    • 空のレイアウト要素なのはViewの削除指定を分かりやすくするためでそれ以外の意味は無いです

という方針で実装してみました。コードは綺麗になりませんでしたが...。

利点としては、エラーメッセージがTextViewとして追加できるので、スタイルの指定が自分のような初心者に取ってやりやすいことですかね...*1

public class MainActivity extends AppCompatActivity {
    private TextInputLayout mTextInputLayout;
    private LinearLayout mErrorMessagWrapper;
    private TextView mErrorMessage;

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

        mTextInputLayout = (TextInputLayout) findViewById(R.id.text_input_layout);
        mTextInputLayout.setError("dummy");
        mTextInputLayout.setErrorEnabled(false);

        mErrorMessagWrapper = (LinearLayout) findViewById(R.id.errorMessagWrapper);
        mErrorMessage = new TextView(this);
        mErrorMessage.setText("空白です");

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mTextInputLayout.getEditText().getText().length() == 0) {
                    mTextInputLayout.setErrorEnabled(true);
                    mTextInputLayout.setError("dummy");
                    mErrorMessagWrapper.addView(mErrorMessage);
                } else {
                    mTextInputLayout.setErrorEnabled(false);
                    mErrorMessagWrapper.removeView(mErrorMessage);
                }
            }
        });

    }
<!-- TextInputLayout以外変更なし -->
    <android.support.design.widget.TextInputLayout
        android:id="@+id/text_input_layout"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        >
        <EditText
            android:id="@+id/userid"
            android:textSize="20sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="ユーザIDを入力"/>
        <LinearLayout
            android:orientation="horizontal"
            android:id="@+id/errorMessagWrapper"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
    </android.support.design.widget.TextInputLayout>

動作は下記のような感じです。

www.gfycat.com

次のバージョンでサポートライブラリのバグ直すとかの話が見えた気がするので、早くバージョンアップしてほしい...。

*1:今回はしてないが! アニメーション等は書けばなんとかなる...?