1

I've been using a reflection method to set my EditText's cursor colors programmatically which I found from this answer (I also tried this answer). However, after some recent updates, can't remember exactly when, that method no longer works, I assume that Android has probably changed something in the TextView class. Anyways, can someone help me with this? Are there now new field names for mCursorDrawableRes and mCursorDrawable, or is that whole method invalid and needs to be implemented another way now?

Update: I just found out that this method stopped working only on Android P, on the previous versions, it still works.

Update 2: I solved problem myself, check the answer if you are stuck as well.

Jack
  • 5,354
  • 2
  • 29
  • 54

2 Answers2

6

OK, after digging into the Android Pie Source Code, I found out that Google has changed mCursorDrawable to mDrawableForCursor, and also changed its type from a two element Drawable array to simply Drawable, so I made some changes based on the original reflection method, and now it works for Android P:

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

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

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

        // Set the drawables
        if(Build.VERSION.SDK_INT >= 28){//set differently in Android P (API 28)
            field = editor.getClass().getDeclaredField("mDrawableForCursor");
            field.setAccessible(true);
            field.set(editor, drawable);
        }else {
            Drawable[] drawables = {drawable, drawable};
            field = editor.getClass().getDeclaredField("mCursorDrawable");
            field.setAccessible(true);
            field.set(editor, drawables);
        }

        //optionally set the "selection handle" color too
        setEditTextHandleColor(editText, color);
    } catch (Exception ignored) {}
}

Side note, I really wish Google could just add a public method like setCursorDrawable() or something like that, that would've been much easier.

Jack
  • 5,354
  • 2
  • 29
  • 54
  • 1
    Thx for the answer! However accessing fields like `mDrawableForCursor` might lead to problems [due to restrictions on non-SDK interfaces](https://developer.android.com/about/versions/pie/restrictions-non-sdk-interfaces). Currently this fix gives me the "Detected problems with API compatibility (visit g.co/dev/appcompat for more info)" error on my Android 9 emulator. – Sebek Nov 28 '18 at 09:46
  • 1
    Yeah I know, I got that warning too, but I can't find any other way to do it. So I guess until the Android team decides to do something about this or someone else find a better solution, we'll have to stick to this way. – Jack Nov 28 '18 at 20:36
  • what a great work you have done!! I'd like to see the android source too... – fatfatson May 01 '19 at 14:38
  • Any other solution? I got this warning too, acessing this king of hidden field got me the warning "Accessing hidden field Landroid/widget/Editor;->mDrawableForCursor:Landroid/graphics/drawable/Drawable; (dark greylist, reflection)" :( – Arthur Melo May 16 '19 at 01:47
1

Unfortunately, Google have not exposed xml attributes to tint, or methods to set, the drawables for these even in the compatability libraries, so currently the only way to dynamically set them is via reflection as described.

However, you can set the drawables in xml, and if you just want to tint the existing material design drawables this can be done by tinting xml for the text select handles as they are bitmap drawables, but the cursor drawable is an inset drawable, so will have to be recreated from the source code.

The drawables used are:

R.drawable.abc_text_select_handle_left_mtrl_light
R.drawable.abc_text_select_handle_middle_mtrl_light
R.drawable.abc_text_select_handle_right_mtrl_light
R.drawable.abc_text_cursor_material

You can create tinted versions of the text select handle drawables like this:

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/abc_text_select_handle_left_mtrl_light"
    android:tint="@color/my_text_select_handle_color" />

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/abc_text_select_handle_middle_mtrl_light"
    android:tint="@color/my_text_select_handle_color" />

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/abc_text_select_handle_right_mtrl_light"
    android:tint="@color/my_text_select_handle_color" />

The cursor drawable can be recreated from the source code like this:

<inset xmlns:android="http://schemas.android.com/apk/res/android"
       android:inset="2dp">
  <shape
      android:tint="@color/my_text_cursor_color"
      android:shape="rectangle">
    <size
        android:height="2dp"
        android:width="2dp" />
    <solid
        android:color="@color/white" />
  </shape>
</inset>

Place these in the drawables folder and reference them in your AppCompatEditText xml definition using:

android:textCursorDrawable
android:textSelectHandle
android:textSelectHandleLeft
android:textSelectHandleRight

and voila, custom colored cursor and select handles that exactly match the default material design version that avoids reflection so won't cause warnings or errors.

  • This is the XML way, I don't need it but others might. Thanks. How did you find this btw? – Jack Feb 01 '19 at 08:09
  • Looking through the sdk and libraries source code.I usually look at SO first, but if I can't find a workable solution, out comes the source code! I've recently been working on a Design System library, so I've doing a lot of that lately in order to meet the UX/UI teams designs. – English Dave Feb 05 '19 at 03:49