TextInputLayout: Different color for hint label wh

2019-03-13 10:11发布

问题:

What I want to do:

When using an EditText embedded in a TextInputLayout I want to ...

  1. Set the Color of the label to GREEN when it's de-focused and floating above the EditText because the user has already entered some value
  2. Set the Color of the label to RED when it's de-focused and located inside the EditText, because the user has not yet entered a value
  3. I do not want to change the hint text color of all my EditTexts to RED, but only when they're wrapped in a TextInputLayout (I don't need a general approach - a specific approach like setting a theme/style for each TextInputLayout in the layout XML would be fine)
  4. Preserve (i.e. don't change) the accent color (YELLOW) used to color the floating label when the user has focused the field.

What I have tried:

Setting the below as a theme/style on the TextInputLayout does satisfy 1. but not 2.

<style name="FloatingLabel" parent="Widget.Design.TextInputLayout">
    <item name="android:textColorHint">@color/red</item>
</style>

Setting a specific color on my embedded EditText that changes the hint text to another color:

 android:textColorHint="@color/text_placeholder_gray"

actually causes an overlap of hint texts when the label is moved from it's floating position back into the Edittext as a hint (i.e. no text).

Setting this:

<style name="TextAppearence.App.TextInputLayout" parent="@android:style/TextAppearance">
<item name="android:textColor">@color/main_color</item>

on the TextInputLayout:

 <android.support.design.widget.TextInputLayout
  ...
   app:hintTextAppearance="@style/TextAppearence.App.TextInputLayout" >

Changes the hint label color but it also does so for the focused state - which means 4 is not satisfied.

And since a picture says more than a tousand words (all fields are in non-focused state):

How to achieve a setup that satisfies criteria 1-4 ?

回答1:

I had a similar problem: I needed to implement a text input layout in which the label has different colours for empty (when there is no text entered in the edit text), "filled" and focused state. My main problem was how to differentiate between the empty and the filled state as setting a different colour for the focused state was already easy using selectors. In the end I decided to define a custom "empty text" state and implement my custom text input layout (which extends the normal text input layout).

Here is some code:

In res/values/attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

...

    <!-- Custom state for the text input layout to determine whether the label is shown above some text or not -->
    <declare-styleable name="EmptyTextState">
        <attr name="state_empty_text" format="boolean"/>
    </declare-styleable>

</resources>

The custom text input layout:

public class EmptyStateTextInputLayout extends TextInputLayout {
    private boolean emptyText = true;
    private static final int[] EMPTY_TEXT_STATE = new int[]{R.attr.state_empty_text};

    public EmptyStateTextInputLayout(Context context) {
        super(context);
    }

    public EmptyStateTextInputLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EmptyStateTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        int[] state = super.onCreateDrawableState(extraSpace + 1);
        if (emptyText) {
            mergeDrawableStates(state, EMPTY_TEXT_STATE);
        }
        return state;
    }

    public void setEmptyTextState(boolean emptyTextState) {
        this.emptyText = emptyTextState;
        refreshDrawableState();
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (child instanceof EditText) {
            EditText editText = (EditText) child;
            if (!TextUtils.isEmpty(editText.getText())) {
                setEmptyTextState(false);
            }
            editText.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

                }

                @Override
                public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

                }

                @Override
                public void afterTextChanged(Editable editable) {
                    if (!TextUtils.isEmpty(editable)) {
                        setEmptyTextState(false);
                    } else {
                        setEmptyTextState(true);
                    }
                }
            });
        }
        super.addView(child, index, params);
    }
}

XML selector to set the colour of label in different states (res/color/input_field_floating_label.xml):

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:color="@color/focused_text_color" android:state_focused="true" />
    <item android:color="@color/placeholder_color" app:state_empty_text="true"/>
    <item android:color="@color/primary_text_color"/> <!-- default color -->
</selector>

Style for the input text layout (in res/values/styles.xml):

<style name="EditTextLayout">
    ...
    <item name="android:textColorHint">@color/input_field_floating_label</item>
</style>

Theme and style for the edit text (still in res/values/styles.xml):

<style name="EditTextTheme">
    ...
    <item name="android:textColorHint">@color/input_field_floating_label</item>
</style>

<style name="EditText">
    <item name="android:theme">@style/EditTextTheme</item>
    ...
</style>

Usage:

<com.package.path.widget.EmptyStateTextInputLayout
            style="@style/DarkEditTextLayout"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            ...
            >

            <EditText
                style="@style/EditText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
</com.package.path.widget.EmptyStateTextInputLayout>

I recommend this blog post to get an idea of working with custom states: http://code.neenbedankt.com/example-of-custom-states-in-android-components/