10

i created a layout for one of my activities in which users can insert a value in some EditText widget. I need that some of these EditText must have a suffix (like cm, mm and so on) that has to be not editable. After the user has inserted the value i will parse the content of these EditText avoiding the suffix so i will handle the only input without the suffix. How to do that?

I have already searched and searched here on SO but nothing helped me. I found answers like this one https://stackoverflow.com/a/20794581/2516399 that don't help me.

I hope i was clear in my question... sorry for my english

Mahozad
  • 18,032
  • 13
  • 118
  • 133
smartmouse
  • 13,912
  • 34
  • 100
  • 166

7 Answers7

16

This is my solution: An EditText class that draws the suffix behind the text. There are two custom attributes for defining the text of the suffix and the suffix padding (to the left corner of the EditText).

public class EditTextWithSuffix extends EditText {
    TextPaint textPaint = new TextPaint();
    private String suffix = "";
    private float suffixPadding;

    public EditTextWithSuffix(Context context) {
        super(context);
    }

    public EditTextWithSuffix(Context context, AttributeSet attrs) {
        super(context, attrs);
        getAttributes(context, attrs, 0);
    }

    public EditTextWithSuffix(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        getAttributes(context, attrs, defStyleAttr);
    }

    @Override
    public void onDraw(Canvas c){
        super.onDraw(c);
        int suffixXPosition = (int) textPaint.measureText(getText().toString()) + getPaddingLeft();
        c.drawText(suffix, Math.max(suffixXPosition, suffixPadding), getBaseline(), textPaint);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        textPaint.setColor(getCurrentTextColor());
        textPaint.setTextSize(getTextSize());
        textPaint.setTextAlign(Paint.Align.LEFT);
    }

    private void getAttributes(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.EditTextWithSuffix, defStyleAttr, 0);
        if(a != null) {
            suffix = a.getString(R.styleable.EditTextWithSuffix_suffix);
            if(suffix == null) {
                suffix = "";
            }
            suffixPadding = a.getDimension(R.styleable.EditTextWithSuffix_suffixPadding, 0);
        }
        a.recycle();
    }
}

here is the attributes definition:

<resources>
    <declare-styleable name="EditTextWithSuffix">
        <attr name="suffix" format="string|reference" />
        <attr name="suffixPadding" format="dimension" />
    </declare-styleable>
</resources>
artkoenig
  • 7,117
  • 2
  • 40
  • 61
  • 1
    excellent solution, just change onDraw() like this to support hint `@Override public void onDraw(Canvas c) { super.onDraw(c); int suffixXPosition = (int) textPaint.measureText(getText().toString()) + getPaddingLeft(); if (getText().toString().isEmpty() && !getHint().toString().isEmpty()) { suffixXPosition = (int) textPaint.measureText(getHint().toString()) + getPaddingLeft(); } c.drawText(suffix, Math.max(suffixXPosition, suffixPadding), getBaseline(), textPaint); }` – Omer Oct 10 '19 at 09:25
  • 1
    I changed the onDraw to show prefix only if there's text otherwise it's adding prefix even when there's hint. I used Kotlin. ` override fun onDraw(c: Canvas) { super.onDraw(c) if (text.toString().isNotEmpty()) { val suffixXPosition = textPaint.measureText(text.toString()).toInt() + paddingLeft c.drawText( suffix, suffixXPosition.toFloat().coerceAtLeast(suffixPadding), baseline.toFloat(), textPaint ) } }` – Sir NIkolay Cesar The First Nov 05 '21 at 16:25
  • @artkoenig Thanks for giving this answer and `Sir NIkolay Cesar The First` for the comment.. – Vivek Thummar Jul 06 '23 at 12:32
12

I have made extension function for EditText:

fun EditText.addSuffix(suffix: String) {
    val editText = this
    val formattedSuffix = " $suffix"
    var text = ""
    var isSuffixModified = false

    val setCursorPosition: () -> Unit =
        { Selection.setSelection(editableText, editableText.length - formattedSuffix.length) }

    val setEditText: () -> Unit = {
        editText.setText(text)
        setCursorPosition()
    }

    this.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(editable: Editable?) {
            val newText = editable.toString()

            if (isSuffixModified) {
                // user tried to modify suffix
                isSuffixModified = false
                setEditText()
            } else if (text.isNotEmpty() && newText.length < text.length && !newText.contains(formattedSuffix)) {
                // user tried to delete suffix
                setEditText()
            } else if (!newText.contains(formattedSuffix)) {
                // new input, add suffix
                text = "$newText$formattedSuffix"
                setEditText()
            } else {
                text = newText
            }
        }

        override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
            charSequence?.let {
                val textLengthWithoutSuffix = it.length - formattedSuffix.length
                if (it.isNotEmpty() && start > textLengthWithoutSuffix) {
                    isSuffixModified = true
                }
            }
        }

        override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
        }
    })
}

You can then use it as:

yourEditTextView.addSuffix("suffix")
netpork
  • 552
  • 7
  • 20
6
private static final String mSuffix = "SUFX";

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        final EditText et = new EditText(this);
        et.setText(mSuffix);

        et.setOnFocusChangeListener(new OnFocusChangeListener() {   
            @Override
            public void onFocusChange(View v, boolean hasFocus) {

                //NO FOCUS
                if(!hasFocus){
                    //HAS USER CLEARED THE SUFFIX
                    if(!et.getText().toString().contains(mSuffix)){
                        //ADDING SUFFIX AGAIN
                        String newText = et.getText().toString();
                        et.setText(mSuffix+newText);
                    }
                }
            }
        });


    }

In my opinion, use Regular Exp. to check the suffix. it will be more secure and simple.

SOLUTION:2

Here is another solution,something tricky ;)

   <RelativeLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
    <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/test_value"
            android:layout_alignParentLeft="true"
            android:id="@+id/editText2"
            android:layout_gravity="center"
            />
    <TextView
            android:id="@+id/text_hint"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:text="@string/hint_test"
            android:textSize="18sp"
            android:layout_marginRight="10dp"
            android:textColor="#808080"
            />
</RelativeLayout>

RESULT/OUTPUT

OUTPUT:

REF:StackOverFlow

theapache64
  • 10,926
  • 9
  • 65
  • 108
  • It seems i cannot use it: `- OnFocusChangeListener cannot be resolved to a type - The method setOnFocusChangeListener(View.OnFocusChangeListener) in the type View is not applicable for the arguments (new OnFocusChangeListener() {})` – smartmouse Feb 24 '15 at 22:23
  • It works and now i see what OnFocusChange does... but it is not what i was looking for. Anyway i really appreciate your reply and i will use it for other purposes! Please have a look at Apurva's answer and try to figure out how to make his solution to work in my case. Thank you – smartmouse Feb 25 '15 at 22:32
  • So you need something like hint, that visible even if the user is typing , right ? – theapache64 Feb 26 '15 at 02:11
  • Solution 2 woks well for me – Kibi Jan 14 '18 at 10:05
  • 1
    Solution 2 is dope. <3 – Md. Rejaul Karim Feb 02 '21 at 04:02
5

Try this

final EditText eTxt = (EditText) findViewById(R.id.edit_text);

eTxt.setText("cm");
Selection.setSelection(eTxt.getText(), eTxt.getText().length());

eTxt.addTextChangedListener(new TextWatcher() {

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // TODO Auto-generated method stub

    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // TODO Auto-generated method stub

    }

    @Override
    public void afterTextChanged(Editable s) {
        if(!s.toString().startsWith("cm")){
            eTxt.setText("cm");
            Selection.setSelection(eTxt.getText(), eTxt.getText().length());
        }

    }
});
Apurva
  • 7,871
  • 7
  • 40
  • 59
  • 1
    It seems the solution but i forgot to say that i have this on my EditText: `.setFilters(new InputFilter[] {new DecimalDigitsInputFilter(2)});` to let user insert only decimal with 2 places. In this case how to make it working? Anyway i used `.endsWith` instead of `.startWith` and then `.setSelection(0);` because it is suffix, not prefix – smartmouse Feb 24 '15 at 22:20
  • Can you not set `android:inputType="number"` and `android:maxLength="2"` in `xml` and then put the prefix in `.class` as I have written in answer? – Apurva Feb 25 '15 at 04:03
  • In my XML file i have only `android:inputType="numberDecimal"` and in the .class file i write this: http://pastebin.com/b442zUt5 As i said if i remove `.setFilters(new InputFilter[] {new DecimalDigitsInputFilter(2)});` it works; if i keep that code it doesn't let me to write input in the EditText, except the suffix that already was there. – smartmouse Feb 25 '15 at 22:23
2

Try Out the following code for numeric edittext with alphabetic suffix:-

expiry.addTextChangedListener(new TextWatcher() {
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // TODO Auto-generated method stub

    }

    @Override
    public void beforeTextChanged(CharSequence c, int start, int count,
                                  int after) {
        // TODO Auto-generated method stub

    }

    @Override
    public void afterTextChanged(Editable e) {
        String s = e.toString();
        if (s.length() > 0) {
            if (!s.endsWith("days")) {
                if (!s.equals(s + "days")) {
                    s = s.replaceAll("[^\\d.]", "");
                    expiry.setText(s + "days");
                } else {
                    expiry.setSelection(s.length() - "days".length());
                }
            } else {
                expiry.setSelection(s.length() - "days".length());
                if (s.equals("days")) {
                    expiry.setText("");
                }
            }
        }
    }
});
1

For anyone working with Kotlin:

class EditTextWithSuffix: EditText {
    private val textPaint = TextPaint()
    private var suffix:String = ""
    private var suffixPadding:Float = 0f

    constructor(context: Context): super(context)

    constructor(context: Context, attrs: AttributeSet): super(context, attrs){
        getAttributes(context, attrs)
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        //I do this check so that the suffix is not drawn on top of the hint if any
        if(this.text.isNotEmpty()) {
            //Get the position that the suffix should be
            val suffixXPosition = textPaint.measureText(this.text.toString()) + paddingLeft + suffixPadding

            //Draw the suffix in the view
            canvas?.drawText(
                suffix,
                max(suffixXPosition, suffixPadding),
                baseline.toFloat(),
                textPaint
            )
        }
    }

    /**
     * Sets the properties of the textPaint to the same as
     * the EditText view
     */
    override fun onFinishInflate() {
        super.onFinishInflate()
        textPaint.color = currentTextColor
        textPaint.textSize = textSize
        textPaint.textAlign = Paint.Align.LEFT
    }

    /**
     * Retrieves the attributes from the layout file
     */
    private fun getAttributes(context: Context, attrs: AttributeSet){
        val typedArray: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.EditTextWithSuffix, 0, 0)

        //Get the suffix attribute
        val theSuffix = typedArray.getString(R.styleable.EditTextWithSuffix_suffix)
        theSuffix?.let {
            suffix = it
        }

        //Get the padding attribute
        suffixPadding = typedArray.getDimension(R.styleable.EditTextWithSuffix_suffixPadding, 0f)

        typedArray.recycle()
    }
}

From: https://stackoverflow.com/a/37278154/3971619

Clément Bisaillon
  • 5,037
  • 8
  • 32
  • 53
1

I have answered a similar question here. As of version 1.2.0-alpha01 of material design library, prefix and suffix is supported for text fields:

<com.google.android.material.textfield.TextInputLayout
        app:prefixText="Price: "
        app:prefixTextAppearance="..."
        app:prefixTextColor="..."
        app:suffixText="Dollar"
        app:suffixTextColor="..."
        app:suffixTextAppearance="...">

    <com.google.android.material.textfield.TextInputEditText .../>

</com.google.android.material.textfield.TextInputLayout>

Note that the suffix is fixed at the end of the text field and does not flow with the input text.

Mahozad
  • 18,032
  • 13
  • 118
  • 133