Lets say we have a simple EditText and I want to change the cursor(caret) to some other color, before we were use reflections to get access to the private fields, but with introduction of Android API Q(29), we can now use textCursorDrawable to set the drawable for the blinking cursor.
Here is the xml code of the EditText
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Test"
android:textSize="30sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Now we can use a WrapDrawable to wrap a ColorDrawable, that will be set as textCursorDrawable value of the EditText, in order for us to change the cursor color.
Here is the code for the WrapDrawable:
class WrapDrawable(color: Int) : Drawable() {
private var drawable = ColorDrawable(color)
@ColorInt
var color: Int = color
set(value) {
field = value
drawable = ColorDrawable(value)
}
override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
super.setBounds(left, top, right, bottom)
drawable.setBounds(left, top, right, bottom)
}
override fun getConstantState(): ConstantState? {
return drawable.constantState
}
override fun setAlpha(alpha: Int) {
drawable.alpha = alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
drawable.colorFilter = colorFilter
}
override fun getOpacity(): Int {
return drawable.alpha
}
override fun draw(canvas: Canvas) {
drawable.draw(canvas)
}
override fun getIntrinsicWidth(): Int {
return drawable.bounds.width()
}
override fun getIntrinsicHeight(): Int {
return drawable.bounds.height()
}
}
In the code below, we change the color of the cursor twice once to Color.RED and second time to Color.BLUE, now we should expect to have a BLUE cursor.
But the problem is that once textCursorDrawable is set, we cannot change it even if we try nullify it.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val text = findViewById<EditText>(R.id.editText)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// set the cursor color to RED
text.textCursorDrawable = WrapDrawable(Color.RED).apply {
setBounds(0, 0, 5, text.lineHeight)
}
// set the cursor color to BLUE !!! NOT WORKING !!!
text.textCursorDrawable = WrapDrawable(Color.BLUE).apply {
setBounds(0, 0, 5, text.lineHeight)
}
}
}
}
So my question is how can we reassign the textCursorDrawable value multiple times?
I have found a workaround by updating the already existing textCursorDrawable value, and changing the ColorDrawable using the color variable.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val text = findViewById<EditText>(R.id.editText)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// set the cursor color to RED
text.textCursorDrawable = WrapDrawable(Color.RED).apply {
setBounds(0, 0, 5, text.lineHeight)
}
// set the cursor color to BLUE
text.textCursorDrawable?.let {
if (it is WrapDrawable) {
it.color = Color.BLUE
it.setBounds(0, 0, 5, text.lineHeight)
}
}
}
}
}

The documentation for
setTextCursorDrawable()states:I have taken a quick look through the TextView and EditText code and haven't determined how to make the change you want. I am not saying that it can't be done; I just don't see it.
Instead, try making a change to your WrapDrawable like this:
This will work and will save the instantiation of a new WrapDrawable.
Update
Can't prove a negative, but it looks like the cursor drawable can't be replaced once set. The following is the reasoning.
For API 31, there are only two places within the TextView code where the cursor drawable is set. The private scope of
mCursorDrawablewill restrict outside access.In TextView.java:
It is the text editor class that the cursor is drawn and it reaches back into the TextView to get the drawable that will be used.
In Editor.java:
loadCursorDrawableis the only place thatmDrawableForCursoris set so, once it is defined, it can't be changed. Since it can't be changed, it can't be set to null to pick up a new cursor drawable that may be defined in the text view.So, the long and the short of this is that the cursor can be changed in
TextViewbut cannot be propagated to the editor.