greywolf82
greywolf82

Reputation: 22173

Change color edittext selection handles programmatically

I'm trying to change the color of an edittext programmatically. It works but as you can see from the image attached, the icons for the text selection are still using the theme color accent instead of the blue I set. How can I change it? My current code is:

editText.setBackgroundTintList(new ColorStateList(new int[][]{StateSet.WILD_CARD}, new int[]{color}));
setCursorDrawableColor(editText, color);

private void setCursorDrawableColor(EditText editText, int color) {
    try {
        Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
        fCursorDrawableRes.setAccessible(true);
        int mCursorDrawableRes = fCursorDrawableRes.getInt(editText);
        Field fEditor = TextView.class.getDeclaredField("mEditor");
        fEditor.setAccessible(true);
        Object editor = fEditor.get(editText);
        Class<?> clazz = editor.getClass();
        Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable");
        fCursorDrawable.setAccessible(true);

        Drawable[] drawables = new Drawable[2];
        Resources res = editText.getContext().getResources();
        drawables[0] = res.getDrawable(mCursorDrawableRes);
        drawables[1] = res.getDrawable(mCursorDrawableRes);
        drawables[0].setColorFilter(color, PorterDuff.Mode.SRC_IN);
        drawables[1].setColorFilter(color, PorterDuff.Mode.SRC_IN);
        fCursorDrawable.set(editor, drawables);
    } catch (final Throwable ignored) {
    }
}

edit text with color accent

Upvotes: 4

Views: 4067

Answers (4)

Şahan Şenvar
Şahan Şenvar

Reputation: 48

Nowadays It's so basic just use this:

myEditText.textSelectHandleLeft?.mutate()
                ?.setTint(ContextCompat.getColor(context, R.color.yourColor))
    
myEditText.textSelectHandleRight?.mutate()
                ?.setTint(ContextCompat.getColor(context, R.color.yourColor))
    
myEditText.textSelectHandle?.mutate()
                ?.setTint(ContextCompat.getColor(context, R.color.yourColor))

And for cursor color:

editText.textCursorDrawable?.mutate()?.setTint(context.getColor(R.color.base_blue_700))

OR FOR XML SOLUTION:

<com.google.android.material.textfield.TextInputEditText
            android:id="@+id/input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxLines="1"
            android:textSelectHandle="@color/white" -> HERE
            android:textSelectHandleLeft="@color/white" -> HERE
            android:textSelectHandleRight="@color/white" -> HERE
            android:fontFamily="@font/montserrat_semibold"
            android:textAlignment="textStart"
            android:textColor="@color/text_color_100"
            android:textSize="14sp" />

It will provide you can change colors without not changing drawable

Upvotes: 0

John
John

Reputation: 1543

Kotlin version, works from api 14 to api 32

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.InsetDrawable
import android.graphics.drawable.RotateDrawable
import android.graphics.drawable.VectorDrawable
import android.os.Build
import android.util.TypedValue
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import java.lang.reflect.Field
import java.lang.Exception
import kotlin.math.sqrt

@SuppressLint("PrivateApi")
fun TextView.setHandlesColor(@ColorInt color: Int) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val size = 22.spToPx(context).toInt()
        val corner = size.toFloat() / 2
        val inset = 10.spToPx(context).toInt()

        //left drawable
        val drLeft = GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, intArrayOf(color, color))
        drLeft.setSize(size, size)
        drLeft.cornerRadii = floatArrayOf(corner, corner, 0f, 0f, corner, corner, corner, corner)
        setTextSelectHandleLeft(InsetDrawable(drLeft, inset, 0, inset, inset))

        //right drawable
        val drRight = GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, intArrayOf(color, color))
        drRight.setSize(size, size)
        drRight.cornerRadii = floatArrayOf(0f, 0f, corner, corner, corner, corner, corner, corner)
        setTextSelectHandleRight(InsetDrawable(drRight, inset, 0, inset, inset))

        //middle drawable
        val drMiddle = GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, intArrayOf(color, color))
        drMiddle.setSize(size, size)
        drMiddle.cornerRadii = floatArrayOf(0f, 0f, corner, corner, corner, corner, corner, corner)
        val mInset = (sqrt(2f) * corner - corner).toInt()
        val insetDrawable = InsetDrawable(drMiddle, mInset, mInset, mInset, mInset)
        val rotateDrawable = RotateDrawable()
        rotateDrawable.drawable = insetDrawable
        rotateDrawable.toDegrees = 45f
        rotateDrawable.level = 10000
        setTextSelectHandle(rotateDrawable)
        return
    }

    try {
        val editorField = TextView::class.java.getFieldByName("mEditor")
        val editor = editorField?.get(this) ?: this
        val editorClass: Class<*> = if (editorField != null) {
            runCatching { Class.forName("android.widget.Editor") }.getOrNull() ?: editorField.javaClass
        } else {
            TextView::class.java
        }
        val handles = listOf(
            "mSelectHandleLeft" to "mTextSelectHandleLeftRes",
            "mSelectHandleRight" to "mTextSelectHandleRightRes",
            "mSelectHandleCenter" to "mTextSelectHandleRes"
        )
        for (i in 0 until handles.size) {
            editorClass.getFieldByName(handles[i].first)?.let { field: Field ->
                val drawable = field.get(editor) as? Drawable
                    ?: TextView::class.java.getFieldByName(handles[i].second)?.getInt(this)
                        ?.let { ContextCompat.getDrawable(context, it) }
                if (drawable != null) field.set(editor, drawable.tinted(color))
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

fun Class<*>.getFieldByName(name: String): Field? = runCatching {
    getDeclaredField(name).apply { isAccessible = true }
}.getOrNull()

fun Number.spToPx(context: Context? = null): Float =
    (context?.resources ?: Resources.getSystem()).displayMetrics
        .let { TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), it) }

fun Drawable.tinted(@ColorInt color: Int): Drawable = when {
    this is VectorDrawableCompat -> {
        this.apply { setTintList(ColorStateList.valueOf(color)) }
    }
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && this is VectorDrawable -> {
        this.apply { setTintList(ColorStateList.valueOf(color)) }
    }
    else -> {
        DrawableCompat.wrap(this)
            .also { DrawableCompat.setTint(it, color) }
            .let { DrawableCompat.unwrap(it) }
    }
}

Upvotes: 4

stepkillah
stepkillah

Reputation: 1

Here is the solution for Xamarin based on Jonh's answer

    public static void SetHandlesColor(EditText editText, Color color)
        {

            try
            {
                if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
                {
                    var size = SpToPx(22, editText.Context);
                    var corner = size / 2f;
                    var inset = SpToPx(10, editText.Context);

                    //left drawable
                    var drLeft = new GradientDrawable(GradientDrawable.Orientation.BottomTop, new[] { (int)color, color });
                    drLeft.SetSize(size, size);
                    drLeft.SetCornerRadii(new[] { corner, corner, 0f, 0f, corner, corner, corner, corner });
                    editText.TextSelectHandleLeft = new InsetDrawable(drLeft, inset, 0, inset, inset);

                    //right drawable
                    var drRight = new GradientDrawable(GradientDrawable.Orientation.BottomTop, new[] { (int)color, color });
                    drRight.SetSize(size, size);
                    drRight.SetCornerRadii(new[] { 0f, 0f, corner, corner, corner, corner, corner, corner });
                    editText.TextSelectHandleRight = new InsetDrawable(drRight, inset, 0, inset, inset);

                    //middle drawable
                    var drMiddle = new GradientDrawable(GradientDrawable.Orientation.BottomTop, new[] { (int)color, color });
                    drMiddle.SetSize(size, size);
                    drMiddle.SetCornerRadii(new[] { 0f, 0f, corner, corner, corner, corner, corner, corner });
                    var mInset = (int)(System.Math.Sqrt(2f) * corner - corner);
                    var insetDrawable = new InsetDrawable(drMiddle, mInset, mInset, mInset, mInset);
                    var rotateDrawable = new RotateDrawable();
                    rotateDrawable.Drawable = insetDrawable;
                    rotateDrawable.ToDegrees = 45f;
                    rotateDrawable.SetLevel(10000);
                    editText.TextSelectHandle = rotateDrawable;
                    return;
                }

                var editorField = Class.FromType(typeof(TextView)).GetDeclaredField("mEditor");
                if (!editorField.Accessible)
                    editorField.Accessible = true;

                var editor = editorField.Get(editText);
                var editorClass = editor.Class;
                string[] handleNames = { "mSelectHandleLeft", "mSelectHandleRight", "mSelectHandleCenter" };
                string[] resNames = { "mTextSelectHandleLeftRes", "mTextSelectHandleRightRes", "mTextSelectHandleRes" };

                for (int i = 0; i < handleNames.Length; i++)
                {
                    var handleField = editorClass.GetDeclaredField(handleNames[i]);
                    if (!handleField.Accessible)
                    {
                        handleField.Accessible = true;
                    }
                    Drawable handleDrawable = (Drawable)handleField.Get(editor);

                    if (handleDrawable == null)
                    {
                        var resField = Class.FromType(typeof(TextView)).GetDeclaredField(resNames[i]);
                        if (!resField.Accessible)
                        {
                            resField.Accessible = true;

                        }
                        int resId = resField.GetInt(editText);
                        handleDrawable = ContextCompat.GetDrawable(editText.Context, resId);

                    }

                    if (handleDrawable != null)
                    {
                        Drawable drawable = handleDrawable.Mutate();
                        drawable.SetColorFilter(color, PorterDuff.Mode.SrcIn);
                        handleField.Set(editor, drawable);
                    }
                }
            }
            catch (ReflectiveOperationException) { }
            catch (Exception ex)
            {
                Crashes.TrackError(ex);
            }
        }

 public static int SpToPx(float sp, Context context)
        {
            return (int)TypedValue.ApplyDimension(ComplexUnitType.Sp, sp, context.Resources.DisplayMetrics);
        }

Upvotes: 0

Alex
Alex

Reputation: 1730

I would prefer to use the styles.xml to style this. However, doing it programmatically can be done as follows:

1. Highlight color

First the highlight color. This can be set by using the following:

editText.setHighlightColor(color);

2. Left and right mark

The left and right mark are still not being colored with this. Continuing with your reflection method, we should do sort of the same for these selection markers:

// Left
Field fCursorDrawableLeftRes = TextView.class.getDeclaredField("mTextSelectHandleLeftRes");
fCursorDrawableLeftRes.setAccessible(true);
int mCursorDrawableLeftRes = fCursorDrawableLeftRes.getInt(editText);

// Right
Field fCursorDrawableRightRes = TextView.class.getDeclaredField("mTextSelectHandleRightRes");
fCursorDrawableRightRes.setAccessible(true);
int mCursorDrawableRightRes = fCursorDrawableRightRes.getInt(editText);

And ofcourse: adding it to the drawables list to update them (updated from your source):

Drawable[] drawables = new Drawable[3];
Resources res = editText.getContext().getResources();
drawables[0] = res.getDrawable(mCursorDrawableRes);
drawables[1] = res.getDrawable(mCursorDrawableLeftRes);
drawables[2] = res.getDrawable(mCursorDrawableRightRes);
drawables[0].setColorFilter(color, PorterDuff.Mode.SRC_IN);
drawables[1].setColorFilter(color, PorterDuff.Mode.SRC_IN);
drawables[2].setColorFilter(color, PorterDuff.Mode.SRC_IN);

3. Result

This means that your method will look something like:

private void setCursorDrawableColor(EditText editText, int color) {
    try {
        Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
        fCursorDrawableRes.setAccessible(true);
        int mCursorDrawableRes = fCursorDrawableRes.getInt(editText);

        // Left
        Field fCursorDrawableLeftRes = TextView.class.getDeclaredField("mTextSelectHandleLeftRes");
        fCursorDrawableLeftRes.setAccessible(true);
        int mCursorDrawableLeftRes = fCursorDrawableLeftRes.getInt(editText);

        // Right
        Field fCursorDrawableRightRes = TextView.class.getDeclaredField("mTextSelectHandleRightRes");
        fCursorDrawableRightRes.setAccessible(true);
        int mCursorDrawableRightRes = fCursorDrawableRightRes.getInt(editText);

        Field fEditor = TextView.class.getDeclaredField("mEditor");
        fEditor.setAccessible(true);
        Object editor = fEditor.get(editText);
        Class<?> clazz = editor.getClass();
        Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable");
        fCursorDrawable.setAccessible(true);

        Drawable[] drawables = new Drawable[3];
        Resources res = editText.getContext().getResources();
        drawables[0] = res.getDrawable(mCursorDrawableRes);
        drawables[1] = res.getDrawable(mCursorDrawableLeftRes);
        drawables[2] = res.getDrawable(mCursorDrawableRightRes);
        drawables[0].setColorFilter(color, PorterDuff.Mode.SRC_IN);
        drawables[1].setColorFilter(color, PorterDuff.Mode.SRC_IN);
        drawables[2].setColorFilter(color, PorterDuff.Mode.SRC_IN);
        fCursorDrawable.set(editor, drawables);
    } catch (final Throwable ignored) {}
}

// Other method (using styles.xml)

As I said, I would prefer to use the styles.xml to achieve this behavior. In this case, only using the following three properties will result in the wanted behavior:

<item name="colorControlNormal">@android:color/holo_green_dark</item>
<item name="colorControlActivated">@android:color/holo_green_dark</item>
<item name="colorControlHighlight">@android:color/holo_green_dark</item>

(And textColorHighlight for highlighting of course)

Upvotes: 2

Related Questions