Limit number of characters entered based on view width

2

I have the following EditText with width defined as match_parent , basically occupying the total width independent of the device:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="end"
    android:maxLines="1"/>

This EditText has a maximum of 1 line and has gravity property as end , as you can see.

I would like to limit the amount of characters of this EditText based on their size. It would look something like:

android:maxLength="largura_total_ocupada_pelo_edit_text"

Is it possible to do this, limit the number of characters entered based on the width of the EditText ?

    
asked by anonymous 03.08.2017 / 22:46

1 answer

5

As was said in the question comments, to do this you would have to measure the size of each character entered in EditText , and this is not as difficult as it seems.

I made it in Kotlin , since since I'm using it in my projects, it would be faster and easier. The conversion to Java will also be available right below.

First, and most importantly, we should know what size the EditText is on the device screen, you can do this measurement using the OnGlobalLayoutListener , because it expects the view to be rendered and then you will have the correct size of it in pixels >. If you try to access the size directly, the returned value will be 0 .

  

This returns zero

int inputSize = ((EditText) findViewById(R.id.inputExample)).getWidth();
  

This returns the width of the component

((EditText) findViewById(R.id.inputExample)).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        inputSize = ((EditText) findViewById(R.id.inputExample)).getWidth();
    }
});

The same goes for the Kotlin language, you will also have to use the same method above, but the code can get a bit more beautiful with some language implementations,

// https://antonioleiva.com/kotlin-ongloballayoutlistener/
inline fun <T: View> T.waitForMeasure(crossinline measure: T.() -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (measuredWidth > 0 && measuredHeight > 0) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                measure()
            }
        }
    })
}

// .. MainActivity()
inputExample.waitForMeasure { // inputExample = é uma bind pra view com o kotlin-android-extensions
    inputWidth = inputExample.measuredWidth
    println("Edittext width (PX) is: $inputWidth")
}

Okay, now we have the size that the component is occupying on the device screen, in the example above, I tested on a Nexus 5X 1080x1920 and the value returned was 1080 . >

So, we can go to the second part, which is to calculate the space that the inserted character occupies on the screen. To do this, we have to use the Paint element of the component itself to measure the character with the method Paint # MeasureText (String) .

  

Java

int inputWidth = 0
float inputSize = 0.0
int maxCharCount = 0

// O tamanho do EditText primeiro :)
((EditText) findViewById(R.id.inputExample)).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        inputSize = ((EditText) findViewById(R.id.inputExample)).getWidth();
    }
});

// A brincadeira começa aqui
((EditText) findViewById(R.id.inputExample)).addTextChangedListener(new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence input, int start, int before, int count) {
            Paint paint = inputExample.getPaint() // EditText ID: inputExample...
            inputSize = paint.measureText(input.toString())

            if (!TextUtils.isEmpty(inputExample.getText().toString())) {
                // Pegar último caractere inserido
                Char char = input[input.length - 1]

               // Pega o espaço restante no EditText
               // Isso depende do tamanho da EditText e do tamanho do TEXTO dela, em pixels.
               // Ex: texto www = 90.0 (w = 30.0) | tela = 1080
               // Espaço restante = 1080-90 = 990
               float spaceLeft = (float) inputWidth - inputSize

               // Pega o tamanho que a letra atual ocupa
               // Ex: letra w = 30.0 ou a = 26
               // Letras maiúsculas e minúsculas diferenciam-se em tamanhos
               // w = 30 | W = 35
               float charSpace = (float) paint.measureText(char.toString())

               if (spaceLeft < charSpace * 1.75) {
                  println("no space left")

                   if (input.length() != maxCharCount) {
                        maxCharCount = input.length()
                   }

               } else {
                   println("hm, there's some space")
                   int charCount = (int) spaceLeft / charSpace
                   maxCharCount = input.length
                   maxCharCount += charCount - (charCount - 1)
               }

               println("Space left: " + spaceLeft)
               println("length: " + maxCharCount)

               // Altera o limite da view de acordo com o espaço restante + tamanho do ultimo caractere inserido (ou o que ainda vai ser inserido)
               // Se tiver espaço: o caractere consegue ser inserido
               inputExample.setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxCharCount) } );
            }
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });
  

Kotlin

var inputWidth: Int = 0
var inputSize: Float
var maxCharCount: Int = 0

inputExample.waitForMeasure {
    inputWidth = inputExample.measuredWidth
    println("Edittext width (PX) is: $inputWidth")
}

val listener = object : TextWatcher {
    override fun afterTextChanged(input: Editable?) {}

    override fun beforeTextChanged(input: CharSequence?, start: Int, count: Int, after: Int) {}

    override fun onTextChanged(input: CharSequence?, start: Int, before: Int, count: Int) {
        val paint = inputExample.paint
        inputSize = paint.measureText(input.toString())

        if (input!!.isNotEmpty()) {
            val char = input[input.length - 1]
            val spaceLeft = inputWidth - inputSize
            val charSpace = paint.measureText(char.toString())

            if (spaceLeft < charSpace * 1.75) {
                println("no space left")
                println("Max char count: $maxCharCount")

                if (input.length != maxCharCount) {
                    maxCharCount = input.length
                }

            } else {
                println("hm, there's some space")
                val charCount = (spaceLeft / charSpace).toInt()
                maxCharCount = input.length
                maxCharCount += charCount - (charCount - 1)
            }

            println("Space left: $spaceLeft")
            println("length: $maxCharCount")

            inputExample.filters = arrayOf<InputFilter>(InputFilter.LengthFilter(maxCharCount))
        }
    }

}

inputExample.addTextChangedListener(listener)
  

Demo

Notes

  • The code was initially made in Kotlin, not Java. So any errors you find, feel free to correct / add a comment.
  • In Kotlin, I used the reference to the EditText component using Kotlin-android-extensions, where I do not need to be using findViewById .
04.08.2017 / 09:05