如何在Android中以编程方式更改Edittext光标颜色?

30

在安卓中,我们可以通过以下方式改变光标颜色:

android:textCursorDrawable="@drawable/black_color_cursor"

那么如何动态地实现这一功能呢?

我的情况是将光标图像设置为白色,但我需要更改为黑色。该如何实现?

    // Set an EditText view to get user input
    final EditText input = new EditText(nyactivity);
    input.setTextColor(getResources().getColor(R.color.black));

请点击这个链接,这是最佳的选择。 - TankRaj
11个回答

87

运用一些反射技术对我很有帮助。

Java:

// https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564
Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
f.setAccessible(true);
f.set(yourEditText, R.drawable.cursor);

XML:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <solid android:color="#ff000000" />

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

</shape>

以下是一种不需要使用XML的方法:

public static void setCursorColor(EditText view, @ColorInt int color) {
  try {
    // Get the cursor resource id
    Field field = TextView.class.getDeclaredField("mCursorDrawableRes");
    field.setAccessible(true);
    int drawableResId = field.getInt(view);

    // Get the editor
    field = TextView.class.getDeclaredField("mEditor");
    field.setAccessible(true);
    Object editor = field.get(view);

    // Get the drawable and set a color filter
    Drawable drawable = ContextCompat.getDrawable(view.getContext(), drawableResId);
    drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
    Drawable[] drawables = {drawable, drawable};

    // Set the drawables
    field = editor.getClass().getDeclaredField("mCursorDrawable");
    field.setAccessible(true);
    field.set(editor, drawables);
  } catch (Exception ignored) {
  }
}

1
如果有人想要更改光标指针颜色,请使用Jared Rummler的方法,但是: 将“mCursorDrawableRes”替换为“mTextSelectHandleRes” 将“mCursorDrawable”替换为“mSelectHandleCenter”如果您还需要在其上进行选择,请相应地使用mTextSelectHandleLeftRes、mTextSelectHandleRightRes和mSelectHandleLeft、mSelectHandleRight请查看TextView.java和Edit.java以获取更多信息。 - Ivanov Maksim
1
API 15中无法工作,java.lang.NoSuchFieldException: mEditor,请问您能提供一些解决方案吗? - Hrishikesh Kadam
2
@PavelPavlov 自 Android P 开始,反射 mDrawableForCursor 已被列入黑名单,如果您仍想更改光标颜色,请使用 xml android:textCursorDrawable 选项。更多信息请参见 https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces。 - cuasodayleo
1
让我再澄清一下。答案是没有XML你做不到。自从Android P以来,他们对反射的某些用途进行了锁定。这意味着即使您可以在Editor中看到mDrawableForCursor属性,但不能像我们在P之前那样访问它。这是来自官方文档的信息,并且适用于所有Android P设备,而不仅仅是PIXEL XL。 - cuasodayleo
我收到一个警告:“当目标API为30及以上时,对mCursorDrawableRes的反射访问将抛出异常”。 - Simple UX Apps
显示剩余4条评论

17
android:textCursorDrawable="@null"

接下来在应用程序中:

final EditText input = new EditText(nyactivity);
input.setTextColor(getResources().getColor(R.color.black));

从这里获取


2
抱歉,我没有任何相关的 XML,应该怎么办? - Amit Prajapati

9

Kotlin 版本,适用于 API 14 到 API 32

import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
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

fun TextView.setCursorDrawableColor(@ColorInt color: Int) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        textCursorDrawable = GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, intArrayOf(color, color))
            .apply { setSize(2.spToPx(context).toInt(), textSize.toInt()) }
        return
    }

    try {
        val editorField = TextView::class.java.getFieldByName("mEditor")
        val editor = editorField?.get(this) ?: this
        val editorClass: Class<*> = if (editorField != null) editor.javaClass else TextView::class.java
        val cursorRes = TextView::class.java.getFieldByName("mCursorDrawableRes")?.get(this) as? Int ?: return

        val tintedCursorDrawable = ContextCompat.getDrawable(context, cursorRes)?.tinted(color) ?: return

        val cursorField = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            editorClass.getFieldByName("mDrawableForCursor")
        } else {
            null
        }
        if (cursorField != null) {
            cursorField.set(editor, tintedCursorDrawable)
        } else {
            editorClass.getFieldByName("mCursorDrawable", "mDrawableForCursor")
                ?.set(editor, arrayOf(tintedCursorDrawable, tintedCursorDrawable))
        }
    } catch (t: Throwable) {
        t.printStackTrace()
    }
}

fun Class<*>.getFieldByName(vararg name: String): Field? {
    name.forEach {
        try{
            return this.getDeclaredField(it).apply { isAccessible = true }
        } catch (t: Throwable) { }
    }
    return null
}

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) }
    }
}

fun Number.spToPx(context: Context? = null): Float {
    val res = context?.resources ?: android.content.res.Resources.getSystem()
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), res.displayMetrics)
}

2
在进行了8年的Android开发之后,我花了几个小时来更改光标的颜色。我只想哭。感谢您的答复! - UneXp
1
@UneXp 虽然更改句柄颜色很难 https://dev59.com/aKfja4cB1Zd3GeqP1tSb#59488928 - John
这个很好用,除了华为手机:huawei/com/android/internal/widget/HwEditor类中没有mDrawableForCursor字段('huawei.com.android.internal.widget.HwEditor'的声明出现在/system/framework/hwEmui.jar中)。@John,你有解决方案吗? - Niklas
1
@Niklas,请尝试编辑代码,现在它可能会正常工作。 - John
它在华为上可以工作。 - Niklas
显示剩余3条评论

7

这是从@Jared Rummler处改写后的函数,进行了一些改进:

  • 支持Android 4.0.x
  • 特殊的getDrawable(Context, int)函数,因为API 22及以上版本中getDrawable(int)已被弃用。
private static final Field
        sEditorField,
        sCursorDrawableField,
        sCursorDrawableResourceField;

static {
    Field editorField = null;
    Field cursorDrawableField = null;
    Field cursorDrawableResourceField = null;
    boolean exceptionThrown = false;
    try {
        cursorDrawableResourceField = TextView.class.getDeclaredField("mCursorDrawableRes");
        cursorDrawableResourceField.setAccessible(true);
        final Class<?> drawableFieldClass;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            drawableFieldClass = TextView.class;
        } else {
            editorField = TextView.class.getDeclaredField("mEditor");
            editorField.setAccessible(true);
            drawableFieldClass = editorField.getType();
        }
        cursorDrawableField = drawableFieldClass.getDeclaredField("mCursorDrawable");
        cursorDrawableField.setAccessible(true);
    } catch (Exception e) {
        exceptionThrown = true;
    }
    if (exceptionThrown) {
        sEditorField = null;
        sCursorDrawableField = null;
        sCursorDrawableResourceField = null;
    } else {
        sEditorField = editorField;
        sCursorDrawableField = cursorDrawableField;
        sCursorDrawableResourceField = cursorDrawableResourceField;
    }
}

public static void setCursorColor(EditText editText, int color) {
    if (sCursorDrawableField == null) {
        return;
    }
    try {
        final Drawable drawable = getDrawable(editText.getContext(), 
                sCursorDrawableResourceField.getInt(editText));
        drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
        sCursorDrawableField.set(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN
                ? editText : sEditorField.get(editText), new Drawable[] {drawable, drawable});
    } catch (Exception ignored) {
    }
}

private static Drawable getDrawable(Context context, int id) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        return context.getResources().getDrawable(id);
    } else {
        return context.getDrawable(id);
    }
}

很好,但我认为最好用ContextCompat.getDrawable(context, resId)替换你自定义的getDrawable(context, resId)方法 - 这个方法已经内置了 ;) - Alexander Krol

3
我们通过以下方式实现了它:
  1. 创建一个布局文件,只包含一个EditText和在xml中设置的光标颜色。
  2. 将其填充
  3. 像使用编程创建的EditText一样使用它

2

我希望在@John的回答基础上进行扩展。我发现在Android版本大于或等于Q时,可以直接使用以下方法而不是使用GradientDrawable:

textCursorDrawable?.tinted(color)

因此,代码变成了:

fun TextView.setCursorDrawableColor(@ColorInt color: Int) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        textCursorDrawable?.tinted(color)
        return
    }

    try {
        val editorField = TextView::class.java.getFieldByName("mEditor")
        val editor = editorField?.get(this) ?: this
        val editorClass: Class<*> = if (editorField != null) editor.javaClass else TextView::class.java
        val cursorRes = TextView::class.java.getFieldByName("mCursorDrawableRes")?.get(this) as? Int ?: return

        val tintedCursorDrawable = ContextCompat.getDrawable(context, cursorRes)?.tinted(color) ?: return

        val cursorField = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            editorClass.getFieldByName("mDrawableForCursor")
        } else {
            null
        }
        if (cursorField != null) {
            cursorField.set(editor, tintedCursorDrawable)
        } else {
            editorClass.getFieldByName("mCursorDrawable", "mDrawableForCursor")
                ?.set(editor, arrayOf(tintedCursorDrawable, tintedCursorDrawable))
        }
    } catch (t: Throwable) {
        t.printStackTrace()
    }
}

fun Class<*>.getFieldByName(vararg name: String): Field? {
    name.forEach {
        try{
            return this.getDeclaredField(it).apply { isAccessible = true }
        } catch (t: Throwable) { }
    }
    return null
}

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) }
    }
}

fun Number.spToPx(context: Context? = null): Float {
    val res = context?.resources ?: android.content.res.Resources.getSystem()
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), res.displayMetrics)
}


1

@Jared Rummler@Oleg Barinov启发,我制作了一种解决方案,它也适用于API 15 -

public static void setCursorColor(EditText editText, @ColorInt int color) {
    try {
        // Get the cursor resource id
        Field field = TextView.class.getDeclaredField("mCursorDrawableRes");
        field.setAccessible(true);
        int drawableResId = field.getInt(editText);

        // Get the drawable and set a color filter
        Drawable drawable = ContextCompat.getDrawable(editText.getContext(), drawableResId);
        drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
        Drawable[] drawables = {drawable, drawable};

        if (Build.VERSION.SDK_INT == 15) {
            // Get the editor
            Class<?> drawableFieldClass = TextView.class;
            // Set the drawables
            field = drawableFieldClass.getDeclaredField("mCursorDrawable");
            field.setAccessible(true);
            field.set(editText, drawables);

        } else {
            // Get the editor
            field = TextView.class.getDeclaredField("mEditor");
            field.setAccessible(true);
            Object editor = field.get(editText);
            // Set the drawables
            field = editor.getClass().getDeclaredField("mCursorDrawable");
            field.setAccessible(true);
            field.set(editor, drawables);
        }
    } catch (Exception e) {
        Log.e(LOG_TAG, "-> ", e);
    }
}

我能以任何方式将此更改应用于我的应用程序中的所有光标可绘制项吗? - Sebek

1
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        if (editText.getTextCursorDrawable() instanceof InsetDrawable) {
            InsetDrawable insetDrawable = (InsetDrawable) editText.getTextCursorDrawable();
            insetDrawable.setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP);
            editText.setTextCursorDrawable(insetDrawable);
        }

        if (editText.getTextSelectHandle() instanceof BitmapDrawable) {
            BitmapDrawable insetDrawable = (BitmapDrawable) editText.getTextSelectHandle();
            insetDrawable.setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP);
            editText.setTextSelectHandle(insetDrawable);
        }

        if (editText.getTextSelectHandleRight() instanceof BitmapDrawable) {
            BitmapDrawable insetDrawable = (BitmapDrawable) editText.getTextSelectHandleRight();
            insetDrawable.setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP);
            editText.setTextSelectHandleRight(insetDrawable);
        }

        if (editText.getTextSelectHandleLeft() instanceof BitmapDrawable) {
            BitmapDrawable insetDrawable = (BitmapDrawable) editText.getTextSelectHandleLeft();
            insetDrawable.setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP);
            editText.setTextSelectHandleLeft(insetDrawable);
        }
    }

在查看Q(29)之前,请参考:https://dev59.com/ulgR5IYBdhLWcg3ww_s0#44352565


你不能直接使用editText.setTextCursorDrawable(ContextCompat.getDrawable(context, R.color.white));吗?因为你已经在>= Android Q上进行了包装。 - Kishan Solanki

0

你应该更改 "colorAccent",为了不改变整个应用程序的这个参数,你可以使用 ThemeOverlay。你可以在这篇文章中阅读更详细的内容,最后一节是 "光标和选择"。


0

这是基于Xamarin的解决方案,参考John的答案

        public static void SetCursorDrawableColor(EditText editText, Color color)
        {
            try
            {
                if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
                {
                    var gradientDrawable = new GradientDrawable(GradientDrawable.Orientation.BottomTop, new[] { (int)color, color });
                    gradientDrawable.SetSize(SpToPx(2, editText.Context), (int)editText.TextSize);
                    editText.TextCursorDrawable = gradientDrawable;
                    return;
                }

                var fCursorDrawableRes =
                    Class.FromType(typeof(TextView)).GetDeclaredField("mCursorDrawableRes");
                fCursorDrawableRes.Accessible = true;
                int mCursorDrawableRes = fCursorDrawableRes.GetInt(editText);
                var fEditor = Class.FromType(typeof(TextView)).GetDeclaredField("mEditor");
                fEditor.Accessible = true;
                Java.Lang.Object editor = fEditor.Get(editText);
                Class clazz = editor.Class;

                if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
                {
                    //TODO This solution no longer works in Android P because of reflection
                    // Get the drawable and set a color filter
                    Drawable drawable = ContextCompat.GetDrawable(editText.Context, mCursorDrawableRes);
                    drawable.SetColorFilter(color, PorterDuff.Mode.SrcIn);
                    var fCursorDrawable = clazz.GetDeclaredField("mDrawableForCursor");
                    fCursorDrawable.Accessible = true;
                    fCursorDrawable.Set(editor, drawable);
                }
                else
                {
                    Drawable[] drawables = new Drawable[2];
                    drawables[0] = ContextCompat.GetDrawable(editText.Context, mCursorDrawableRes).Mutate();
                    drawables[1] = ContextCompat.GetDrawable(editText.Context, mCursorDrawableRes).Mutate();
                    drawables[0].SetColorFilter(color, PorterDuff.Mode.SrcIn);
                    drawables[1].SetColorFilter(color, PorterDuff.Mode.SrcIn);

                    var fCursorDrawable = clazz.GetDeclaredField("mCursorDrawable");
                    fCursorDrawable.Accessible = true;
                    fCursorDrawable.Set(editor, drawables);
                }
            }
            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);
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接