Adding a prefix to TextInputEditText
Adding a prefix to TextInputEditText which is always visible is surprisingly hard with TextInputLayout. Handling this in a TextWatcher is a lot of code. However, there is an easier way!
Using a TextWatcher to add an always visible prefix is fairly complicated, if someone clicks in the middle of your prefix, you have to intercept the text and move it to the end or more the cursor to the end immediately. All of this can result in a LOT of code. You need to disable copy/paste, you need to handle onClick, you need to handle text change, etc.
It’s tricky to align the background text and the EditText. The reason for that is that padding is in DIPs and text sizes are in SP. Look at compatibility issues here for more on EditText fragmentation. An easier approach would be to extend TextInputEditText which means create your custom PrefixEditText.
It does support prefix text changing. Also, I haven’t checked right to left displays but that should be simple enough.
The code
I am using tempPrefix because if you set prefix before it gets focus then prefix will be visible for all time. Using onFocusChanged listener you can set prefix and then it will be visible on focus to this view.
class PrefixEditText(context: Context, attributes: AttributeSet?) :
TextInputEditText(context, attributes) {
private var mOriginalLeftPadding: Float = -1f
private var prefix = ""
private var tempPrefix = ""
init {
context.obtainStyledAttributes(attributes, R.styleable.PrefixEditText).apply {
getString(R.styleable.PrefixEditText_prefix)?.let { setPrefixes(it) }
}.recycle()
}
fun setPrefixes(fix: String) {
tempPrefix = fix
}
override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
super.onFocusChanged(focused, direction, previouslyFocusedRect)
if (focused)
prefix = tempPrefix
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
calculatePrefix()
}
private fun calculatePrefix() {
if (mOriginalLeftPadding == -1f && prefix.isNotEmpty()) {
val widths = FloatArray(prefix.length)
paint.getTextWidths(prefix, widths)
var textWidth = 0f
for (w in widths) {
textWidth += w
}
mOriginalLeftPadding = compoundPaddingLeft.toFloat()
setPadding(
(textWidth + mOriginalLeftPadding).toInt(),
paddingRight, paddingTop,
paddingBottom
)
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (prefix.isNotEmpty())
canvas?.drawText(
prefix, mOriginalLeftPadding,
getLineBounds(0, null).toFloat(), paint
)
}
}
The XML
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/amountLayout"
style="@style/EliqInputLayoutStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_standard"
android:hint="Amount"
android:minWidth="@dimen/buttonWidthNormal">
<io.eliq.core.ui_components.PrefixEditText
android:id="@+id/etAmount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
The styleable attribute
<resources><declare-styleable name="PrefixEditText">
<attr name="prefix" format="string" />
</declare-styleable></resources>
Usage
etAmount.setPrefixes(viewModel.getCurrencySymbol())
Prefix
I am using the currency instance from NumberFormat in this case.
fun getCurrencySymbol(): String = NumberFormat.getCurrencyInstance().currency?.symbol ?: ""
Yayyy! That’s all. I believe you would be able to tweak according to your needs.