I think that PhúcNguyễn had a good idea to marry a TextView with an EditText to produce what you are looking for. You could place these as separate fields in a layout or place them in a composite view. Either way the effect will be the same and you could achieve what you want.
You have already figured out how to handle the static text at the beginning of the field. What I am presenting below is how to handle the underscores so characters that are entered appear to overwrite the underscores.
For the demo, I have place a TextView with the static text beside a custom EditText. It is the custom EditText that is really of any interest. With the custom view, the onDraw() function is overridden to write the underscores as part of the background. Although these underscores will appear like any other character in the field, they cannot be selected, deleted, skipped over or manipulated in any way except, as the user types, the underscores are overwritten one-by-one. The end padding of the custom view is manipulated to provide room for the underscores and text.

Here is the custom view:
EditTextFillInBlanks.kt
class EditTextFillInBlanks @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatEditText(context, attrs, defStyleAttr) {
// Right padding before we manipulate it
private var mBaseRightPadding = 0
// Width of text that has been entered
private var mTextWidth = 0f
// Mad length of data that can be entered in characters
private var mMaxLength = 0
// The blanks (underscores) that we will show
private lateinit var mBlanks: String
// MeasureSpec for measuring width of entered characters.
private val mUnspecifiedWidthHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
init {
mBaseRightPadding = paddingRight
doOnTextChanged { text, _, _, _ ->
measure(mUnspecifiedWidthHeight, mUnspecifiedWidthHeight)
mTextWidth = measuredWidth.toFloat() - paddingStart - paddingEnd
updatePaddingForBlanks(text)
}
setText("", BufferType.EDITABLE)
}
/*
Make sure that the end padding is sufficient to hold the blanks that we are showing.
The blanks (underscores) are written into the expanded padding.
*/
private fun updatePaddingForBlanks(text: CharSequence?) {
if (mMaxLength <= 0) {
mMaxLength = determineMaxLen()
check(mMaxLength > 0) { "Maximum length must be > 0" }
}
text?.apply {
val blanksCount = max(0, mMaxLength - length)
mBlanks = "_".repeat(blanksCount).apply {
updatePadding(right = mBaseRightPadding + paint.measureText(this).toInt())
}
}
}
/*
Draw the underscores on the canvas. They will appear as characters in the field but
cannot be manipulated by the user.
*/
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (mBlanks.isNotEmpty()) {
canvas?.withSave {
drawText(mBlanks, paddingStart + mTextWidth, baseline.toFloat(), paint)
}
}
}
fun setMaxLen(maxLen: Int) {
mMaxLength = maxLen
}
private fun determineMaxLen(): Int {
// Before Lollipop, we can't get max for InputFilter.LengthFilter
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return 0
return filters.firstOrNull { it is InputFilter.LengthFilter }
?.let {
it as InputFilter.LengthFilter
it.max
} ?: 0
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_light"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:text="+12345"
android:textColor="@android:color/black"
android:textSize="36sp"
app:layout_constraintBaseline_toBaselineOf="@id/editableSuffix"
app:layout_constraintEnd_toStartOf="@+id/editableSuffix"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="@+id/guideline2" />
<com.example.edittextwithblanks.EditTextFillInBlanks
android:id="@+id/editableSuffix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/edittext_background"
android:inputType="number"
android:maxLength="@integer/blankFillLen"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:textColor="@android:color/black"
android:textSize="36sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView"
app:layout_constraintTop_toTopOf="parent"
tools:text="____">
<requestFocus />
</com.example.edittextwithblanks.EditTextFillInBlanks>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="92dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val mStaticStart = "+12345"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (BuildConfig.VERSION_CODE < Build.VERSION_CODES.P) {
val maxLen = resources.getInteger(R.integer.blankFillLen)
findViewById<EditTextFillInBlanks>(R.id.editableSuffix).setMaxLen(maxLen)
}
}
}
It is likely that you can incorporate your static text handling into the custom view for a complete solution.