Limit amount of typed characters based on view width

Asked

Viewed 940 times

2

I got the following EditText width defined as match_parent, basically occupying the full width independent of the device:

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

That one EditText has a maximum of 1 line and is owned gravity as end, as you can see.

I would like to limit the number of characters in this EditText based on its size. It would be something like:

android:maxLength="largura_total_ocupada_pelo_edit_text"

You can do this, limit the amount of typed characters based on the width of the EditText?

  • I was intrigued, why do you want to do this, rs?

  • Anyway, my guess would be to use a font with equal spacing between all characters, make a Asure in Edittext to pick the width in pixels and calculate the amount of characters that would fit in that space by the width of the same pixels.

  • @Márciooliveira the question was really curious to see how many ways it would be possible to do this.

  • Because I think the biggest difficulty to do with the native font is that each character has its own spacing and you can’t predict what the user will type, so you would have to do the same calculation that I just suggested using a broad character, ex "O".

1 answer

5


As stated in the comments of the question, to do this you would have to measure the size of each character inserted in the EditText, and it’s not as hard as it looks.

I did in Kotlin, because since I am using it in my projects, it would be faster and easier. The conversion to Java will also be available just below.

First, and most importantly, we should know how big the EditText present on the device screen, you can make this measurement using the method Ongloballayoutlistener, because he hopes that the view be rendered and then you will have the correct size of it, in pixels. If you try to access its 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 language Kotlin, you will also have to use the same method above, but the code can get a little more beautiful with some language implementations such as:

// 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")
}

Ready, 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.

Thus, 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 class Paint 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)

Demonstration

inserir a descrição da imagem aqui


Remarks

  • The code was initially made in Kotlin, not Java. So, any mistakes you encounter, feel free to correct/add a comment.
  • In Kotlin, I used the component reference Edittext using Kotlin-android-Extensions, where I don’t need to be using findViewById.
  • I haven’t tested it yet. So when you do, validate your answer Luc. Vlw. But I’ve already left a +1 for the performance

  • I tested with some variations of words/ letters and the result was very satisfactory. I didn’t do a rough test, so I can’t tell you if it’s perfect or not, but I know it can get better... so feel free to ;p

Browser other questions tagged

You are not signed in. Login or sign up in order to post.