Material-components-android: Change the cursor color via styling

Created on 11 Dec 2019  Â·  21Comments  Â·  Source: material-components/material-components-android

Looks like the TextInputLayout and TextInputEditTextLayout are defaulting the cursor (and cursor bubble) color to primary color. I tested it by switching the global primary color of the app and it works. However, when I try to create a theme for that specific view and override the primary color, it doesn't work.

<style name="Widget.AppTheme.TextInputEditText.Dense" parent="Widget.MaterialComponents.TextInputEditText.FilledBox.Dense">
        <item name="colorPrimary">@color/orange</item>
</style>
        <com.google.android.material.textfield.TextInputLayout
            app:hintEnabled="false"
            app:hintAnimationEnabled="false"
            android:id="@+id/f_login_email_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/margin_default"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/f_login_subheader">

            <com.google.android.material.textfield.TextInputEditText
                style="@style/Widget.AppTheme.TextInputEditText.Dense"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="12dp"
                android:ems="10"
                android:textSize="@dimen/text_size_text_entry"
                android:hint="@string/login_email_hint"
                android:inputType="textEmailAddress" />
        </com.google.android.material.textfield.TextInputLayout>

Could somebody help with this?

Most helpful comment

Hi everyone
I share my way of setting the OutlinedBox style.

First of all, I am using version 1.1.0

implementation 'com.google.android.material:material:1.1.0'

Just like you mentioned @dsn5ft , I create a style by referencing the style TextInputEditText.OutlinedBox

<style name="exampleCursor" parent="ThemeOverlay.MaterialComponents.TextInputEditText.OutlinedBox">
    <item name="colorControlActivated">@color/green</item>
</style>

In my case I use a style for my TextInputLayout, I add android:theme with the created style

<style name="TextInputLayoutStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
   <item name="android:background">@android:color/transparent</item>
    <item name="android:textColorHint">@color/colorAccent</item>
    <item name="android:textColor">@color/colorAccent</item>
    <item name="hintTextColor">@color/colorAccent</item>
    <item name="boxStrokeColor">@color/silver</item>
    <item name="boxStrokeWidth">1dp</item>
    <item name="errorEnabled">true</item>
    <item name="counterEnabled">false</item>
    <item name="android:theme">@style/exampleCursor</item>
</style>

and I add the style in the component

<com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/TextInputLayoutStyle"
….

and that's it, the color change to the cursor works

screenshots_device

regards.

All 21 comments

UPDATE

If I create the style for the TextInputLayout and define the colorControlActivated like

    <style name="Widget.AppTheme.TextInputLayout.FilledBox.Dense" parent="Widget.MaterialComponents.TextInputLayout.FilledBox.Dense">
        <item name="errorIconDrawable">@null</item>
        <item name="hintTextColor">@color/black</item>
        <item name="boxBackgroundColor">@color/greyLight</item>
        <item name="boxStrokeWidth">0dp</item>
        <item name="errorTextColor">@color/orange</item>
        <item name="colorControlActivated">@color/orange</item>
    </style>

and use theming instead of styling on the TextInputLayout

        <com.google.android.material.textfield.TextInputLayout
            app:hintEnabled="false"
            app:hintAnimationEnabled="false"
            android:id="@+id/f_login_email_layout"
            android:theme="@style/Widget.AppTheme.TextInputLayout.FilledBox.Dense"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/margin_default"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/f_login_subheader">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/f_login_email"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="12dp"
                android:ems="10"
                android:textSize="@dimen/text_size_text_entry"
                android:hint="@string/login_email_hint"
                android:inputType="textEmailAddress" />
        </com.google.android.material.textfield.TextInputLayout>

It suddenly works perfectly. But I don't really want to use theming because I have to copy that line all over the place while using styling I can simply use <item name="editTextStyle">@style/Widget.AppTheme.TextInputEditText.Dense</item>

I agree this is a bug and I've been looking for a way around it for the past week. I was actually about to finally log a bug when I found your issue already logged. I'll give your workaround a try...but I completely agree that I shouldn't have to do it that way.

It might also be important to point out that if an app has existing EditText controls those will use colorAccent while TextInputLayout will use colorPrimary. An app could easily have a mixture of cursor colors. Basically, all legacy Android controls (that I know of) have always used colorAccent for the cursor color. I feel that should be the default for new controls as well.

The reason why using android:theme works is because colorControlActivated is a theme attribute, as opposed to a specific component attribute (e.g., hintTextColor).

One way you could customize a global theme attribute but only have that customization apply to a specific component is to use our materialThemeOverlay functionality. E.g.:

Create a theme overlay that customizes colorControlActivated:

  <style name="ThemeOverlay.AppTheme.TextInputEditText.FilledBox.Dense" parent="ThemeOverlay.MaterialComponents.TextInputEditText.FilledBox.Dense">
    <item name="colorControlActivated">@color/orange</item>
  </style>

Create a TextInputLayout style that uses the theme overlay:

  <style name="Widget.AppTheme.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox.Dense">
    <item name="materialThemeOverlay">@style/ThemeOverlay.AppTheme.TextInputEditText.FilledBox.Dense</item>
  </style>

Set the TextInputLayout style as the default in your app theme:

  <item name="textInputStyle">@style/Widget.AppTheme.TextInputLayout</item>

@dsn5ft it doesn't seem to work

Apologies, there was a typo in my comment above. Make sure the materialThemeOverlay in the TextInputLayout style actually points to the AppTheme theme overlay.

Also, make sure to remove any android:themes and/or styles from your specific TextInputLayout instance.

ah thanks, I also figured out 😄

This was never an issue before in v1.0.0. Why this is changed in v1.1.0? It has made things very complicated now. For OutlinedBox, this has totally messed up the things. If I apply the style as a theme then it makes my box appear with bottom outline only with grey background.

How to apply fix for OutlinedBox?

@zishanj the solution given by @dsn5ft should also work for the outlined text field as long as you have the parents set to the OutlinedBox styles instead of the FilledBox styles. Does it not work for you?

My bad I missed one style but it has worked though. Btw. Is there a shorthand way to change colorPrimary of any component using colorPrimary attribute or something similar?

you mean you want to change colorPrimary for your entire app? You can set it on your own theme like:

<style name="Theme.YourTheme" parent="Theme.MaterialComponents.Light">
   <item name="colorPrimary">@color/yourColor</item>
</style>

Not for entire app but for specific component like TextInputLayout in this case which I want to change to colorAccent.

@zishanj I think you can create a style and apply it to materialThemeOverlay as @dsn5ft pointed out above

Why couldn't leave colorAccent for this as before? Now we have to write this workaround to get what was originally expected of TextInputEditText.

I also confirm that the solution provided from @dsn5ft works for OutlinedBox, but like @akhbulatov has pointed - there is a lot of work to do for a simple styling ...

Hi everyone
I share my way of setting the OutlinedBox style.

First of all, I am using version 1.1.0

implementation 'com.google.android.material:material:1.1.0'

Just like you mentioned @dsn5ft , I create a style by referencing the style TextInputEditText.OutlinedBox

<style name="exampleCursor" parent="ThemeOverlay.MaterialComponents.TextInputEditText.OutlinedBox">
    <item name="colorControlActivated">@color/green</item>
</style>

In my case I use a style for my TextInputLayout, I add android:theme with the created style

<style name="TextInputLayoutStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
   <item name="android:background">@android:color/transparent</item>
    <item name="android:textColorHint">@color/colorAccent</item>
    <item name="android:textColor">@color/colorAccent</item>
    <item name="hintTextColor">@color/colorAccent</item>
    <item name="boxStrokeColor">@color/silver</item>
    <item name="boxStrokeWidth">1dp</item>
    <item name="errorEnabled">true</item>
    <item name="counterEnabled">false</item>
    <item name="android:theme">@style/exampleCursor</item>
</style>

and I add the style in the component

<com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/TextInputLayoutStyle"
….

and that's it, the color change to the cursor works

screenshots_device

regards.

Is there a way to color the selection? I'm really sorry but coming from iOS, I feel like Android styling is so much more convoluted.

Feature request:

<item name="cursorColor">>@color/colorAccent</item>

😂

I'm going to have to write my own component because setting the line color and the bubble color also changes the selection popup background color on some phones 😂 colorControlActivated drives literally everything

If anyone needs something like it (no error management cuz I was lazy):

view_custom_input.xml:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <EditText
        android:id="@+id/textInputField"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:background="@drawable/custom_input_edittext_background"
        android:textCursorDrawable="@drawable/edittext_caret"
        android:maxLines="3"
        android:saveEnabled="false" />

    <TextView
        android:id="@+id/textHintLabel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="false"
        android:focusable="false"
        android:padding="4dp"
        android:textColor="@color/textCaption"
        android:translationY="10dp"
        tools:text="Comment" />

    <TextView
        android:id="@+id/textCharacterCountIndicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="8dp"
        android:textColor="@color/textCharacterCount"
        android:textSize="12sp"
        tools:text="35/35" />
</merge>

CustomInputView.kt:

class CustomInputView : FrameLayout {
    constructor(context: Context) : super(context) {
        init(context, null)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context, attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        init(context, attrs)
    }

    private var hintText: String = ""
    private var maxCharacterCount: Int = 70

    private lateinit var binding: ViewCustomInputBinding

    private fun init(context: Context, attrs: AttributeSet?) {
        LayoutInflater.from(context).inflate(R.layout.view_custom_input, this, true)

        clipToPadding = false

        updatePadding(left = dp(16), right = dp(16), top = dp(24))

        binding = ViewCustomInputBinding.bind(this)

        if (attrs != null) {
            val a = context.obtainStyledAttributes(attrs, R.styleable.CustomInputView)
            if (a.hasValue(R.styleable.CustomInputView_hintText)) {
                hintText = a.getString(R.styleable.CustomInputView_hintText) ?: ""

                binding.textHintLabel.text = hintText
            }
            if (a.hasValue(R.styleable.CustomInputView_maxCharacterCount)) {
                maxCharacterCount = a.getInt(R.styleable.CustomInputView_maxCharacterCount, 0)
            }

            a.recycle()
        }
    }

    val editText: EditText
        get() = binding.textInputField

    val text: String
        get() = editText.text.toString()

    private var isLabelOnTop = false

    var onTextChanged: ((String) -> Unit)? = null

    override fun onFinishInflate() {
        super.onFinishInflate()

        val textHint = binding.textHintLabel
        val textInput = binding.textInputField
        val textCharacterCountIndicator = binding.textCharacterCountIndicator

        fun moveLabelToTop() {
            textHint.objectAnimate()
                .translationY(dp(-16).f)
                .translationX(dp(-8).f)
                .scales(0.825f)
                .setDuration(100L)
                .onAnimationEnd {
                    isLabelOnTop = true
                }
                .start()
        }

        fun moveLabelToBottom() {
            textHint.objectAnimate()
                .translationY(dp(10).f)
                .translationX(0f)
                .scales(1f)
                .setDuration(100L)
                .onAnimationEnd {
                    isLabelOnTop = false
                }
                .start()
        }

        textInput.onFocusChange { view, hasFocus ->
            val text = view.text.toString()

            if (text.isEmpty() && isLabelOnTop) {
                moveLabelToBottom()
            }

            if ((hasFocus && !isLabelOnTop) || (text.isNotEmpty() && !isLabelOnTop)) {
                moveLabelToTop()
            }
        }

        textCharacterCountIndicator.text = "${text.length}/${maxCharacterCount}"

        textInput.onTextChanged { text ->
            textCharacterCountIndicator.text = "${text.length}/${maxCharacterCount}"

            onTextChanged?.invoke(text)
        }
    }

    override fun onSaveInstanceState(): Parcelable =
        SavedState(super.onSaveInstanceState()).apply {
            currentText = text
        }

    override fun onRestoreInstanceState(state: Parcelable) {
        val savedState = state as SavedState
        super.onRestoreInstanceState(savedState.superState)

        editText.setText(state.currentText)

        if (state.currentText.isNotEmpty()) {
            isLabelOnTop = true

            binding.textHintLabel.apply {
                translationY = dp(-16).f
                translationX = dp(-8).f
                scaleX = 0.825f
                scaleY = 0.825f
            }
        }
    }

    class SavedState : BaseSavedState {
        var currentText: String = ""

        constructor(superState: Parcelable?) : super(superState) {}

        override fun writeToParcel(out: Parcel, flags: Int) {
            super.writeToParcel(out, flags)
            out.writeString(currentText)
        }

        private constructor(`in`: Parcel) : super(`in`) {
            currentText = `in`.readString() ?: ""
        }

        companion object {
            @JvmField
            val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
                override fun createFromParcel(`in`: Parcel): SavedState {
                    return SavedState(`in`)
                }

                override fun newArray(size: Int): Array<SavedState?> {
                    return arrayOfNulls(size)
                }
            }
        }
    }
}

edittext_caret.xml (not sure if this even does anything tbh):

<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:insetTop="1dp"
    android:insetBottom="1dp">

    <shape android:shape="rectangle">
        <size android:width="1dp" />

        <solid android:color="@color/colorAccent" />

        <padding
            android:bottom="-1dp"
            android:top="-1dp" />
    </shape>
</inset>

custom_input_edittext_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:insetLeft="4dp"
    android:insetTop="10dp"
    android:insetRight="4dp"
    android:insetBottom="7dp">
    <selector>
        <item android:state_pressed="true">
            <layer-list>
                <item
                    android:bottom="1dp"
                    android:left="-6dp"
                    android:right="-6dp"
                    android:top="-12dp">
                    <shape android:shape="rectangle">
                        <stroke
                            android:width="1dp"
                            android:color="@color/customInputActiveLine" />

                        <solid android:color="#00FFFFFF" />

                        <padding
                            android:bottom="7dp"
                            android:left="3dp"
                            android:right="3dp"
                            android:top="8dp" />
                    </shape>
                </item>
            </layer-list>
        </item>
        <item android:state_focused="true">
            <layer-list>
                <item
                    android:bottom="1dp"
                    android:left="-6dp"
                    android:right="-6dp"
                    android:top="-12dp">
                    <shape android:shape="rectangle">
                        <stroke
                            android:width="1dp"
                            android:color="@color/customInputActiveLine" />

                        <solid android:color="#00FFFFFF" />

                        <padding
                            android:bottom="7dp"
                            android:left="3dp"
                            android:right="3dp"
                            android:top="8dp" />
                    </shape>
                </item>
            </layer-list>
        </item>
        <item>
            <layer-list>
                <item
                    android:bottom="1dp"
                    android:left="-6dp"
                    android:right="-6dp"
                    android:top="-12dp">
                    <shape android:shape="rectangle">
                        <stroke
                            android:width="1dp"
                            android:color="@color/customInputInactiveLine" />

                        <solid android:color="#00FFFFFF" />

                        <padding
                            android:bottom="7dp"
                            android:left="3dp"
                            android:right="3dp"
                            android:top="8dp" />
                    </shape>
                </item>
            </layer-list>
        </item>
    </selector>
</inset>

attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomInputView">
        <attr name="hintText" format="string" />
        <attr name="maxCharacterCount" format="integer" />
    </declare-styleable>
</resources>

I'm sure whoever needs more can fill in the blanks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JakeWharton picture JakeWharton  Â·  3Comments

sepehr-alipour picture sepehr-alipour  Â·  3Comments

ataulm picture ataulm  Â·  3Comments

magnusfernandes picture magnusfernandes  Â·  3Comments

mnayef95 picture mnayef95  Â·  3Comments