Reputation: 22173
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) {
}
}
Upvotes: 4
Views: 4067
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
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
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
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