¿Cómo ajustar el interletraje de texto en Android TextView?

¿Hay alguna forma de ajustar el espacio entre caracteres en un Android TextView ? Creo que esto se suele llamar “kerning”.

Soy consciente del atributo android:textScaleX , pero eso comprime los caracteres junto con el espaciado.

AFAIK, no puedes ajustar el interletraje en TextView . Es posible que pueda ajustar el interletraje si dibuja el texto en el Canvas usando las API de gráficos en 2D.

Creé una clase personalizada que amplía TextView y agrega un método “setSpacing”. La solución es similar a lo que dijo @Noah. El método agrega un espacio entre cada letra del String y con SpannedString cambia el TextScaleX de los espacios, lo que permite el espaciado positivo y negativo.

Espero que ayude a alguien ^^

 /** * Text view that allows changing the letter spacing of the text. * * @author Pedro Barros (pedrobarros.dev at gmail.com) * @since May 7, 2013 */ import android.content.Context; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ScaleXSpan; import android.util.AttributeSet; import android.widget.TextView; public class LetterSpacingTextView extends TextView { private float spacing = Spacing.NORMAL; private CharSequence originalText = ""; public LetterSpacingTextView(Context context) { super(context); } public LetterSpacingTextView(Context context, AttributeSet attrs){ super(context, attrs); } public LetterSpacingTextView(Context context, AttributeSet attrs, int defStyle){ super(context, attrs, defStyle); } public float getSpacing() { return this.spacing; } public void setSpacing(float spacing) { this.spacing = spacing; applySpacing(); } @Override public void setText(CharSequence text, BufferType type) { originalText = text; applySpacing(); } @Override public CharSequence getText() { return originalText; } private void applySpacing() { if (this == null || this.originalText == null) return; StringBuilder builder = new StringBuilder(); for(int i = 0; i < originalText.length(); i++) { builder.append(originalText.charAt(i)); if(i+1 < originalText.length()) { builder.append("\u00A0"); } } SpannableString finalText = new SpannableString(builder.toString()); if(builder.toString().length() > 1) { for(int i = 1; i < builder.toString().length(); i+=2) { finalText.setSpan(new ScaleXSpan((spacing+1)/10), i, i+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } super.setText(finalText, BufferType.SPANNABLE); } public class Spacing { public final static float NORMAL = 0; } } 

Utilizándolo:

 LetterSpacingTextView textView = new LetterSpacingTextView(context); textView.setSpacing(10); //Or any float. To reset to normal, use 0 or LetterSpacingTextView.Spacing.NORMAL textView.setText("My text"); //Add the textView in a layout, for instance: ((LinearLayout) findViewById(R.id.myLinearLayout)).addView(textView); 

Si alguien está buscando una forma simple de aplicar el kerning a cualquier cadena (técnicamente, CharSequence ) sin usar un TextView :

 public static Spannable applyKerning(CharSequence src, float kerning) { if (src == null) return null; final int srcLength = src.length(); if (srcLength < 2) return src instanceof Spannable ? (Spannable)src : new SpannableString(src); final String nonBreakingSpace = "\u00A0"; final SpannableStringBuilder builder = src instanceof SpannableStringBuilder ? (SpannableStringBuilder)src : new SpannableStringBuilder(src); for (int i = src.length() - 1; i >= 1; i--) { builder.insert(i, nonBreakingSpace); builder.setSpan(new ScaleXSpan(kerning), i, i + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } return builder; } 

Aquí está mi solución, que agrega espacio uniforme (en píxeles) entre cada personaje. Este lapso supone que todo el texto está en una sola línea. Esto básicamente implementa lo que sugiere @commonsWare.

 SpannableStringBuilder builder = new SpannableStringBuilder("WIDE normal"); builder.setSpan(new TrackingSpan(20), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ... private static class TrackingSpan extends ReplacementSpan { private float mTrackingPx; public TrackingSpan(float tracking) { mTrackingPx = tracking; } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { return (int) (paint.measureText(text, start, end) + mTrackingPx * (end - start - 1)); } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { float dx = x; for (int i = start; i < end; i++) { canvas.drawText(text, i, i + 1, dx, y, paint); dx += paint.measureText(text, i, i + 1) + mTrackingPx; } } } 

La única forma que encontré para ajustar el interletraje es crear una fuente personalizada en la que se altere el avance del glifo.

También puede intentar usar un SpannedString pero necesitaría analizarlo y cambiar el espaciado entre caracteres para cada una de las palabras

Esta respuesta puede ser útil para alguien que quiere dibujar texto con interletraje en un canvas, usando drawText (esto no se trata de texto en un TextView).

Desde Lollipop, el método setLetterSpacing está disponible en Paint. Si el SDK es LOLLIPOP y está activado, se usa setLetterSpacing. De lo contrario, se invoca un método que hace algo similar a la sugerencia de @dgmltn anterior:

  if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { paint.setLetterSpacing(-0.04f); // setLetterSpacing is only available from LOLLIPOP and on canvas.drawText(text, xOffset, yOffset, paint); } else { float spacePercentage = 0.05f; drawKernedText(canvas, text, xOffset, yOffset, paint, spacePercentage); } /** * Progtwigtically drawn kerned text by drawing the text string character by character with a space in between. * Return the width of the text. * If canvas is null, the text won't be drawn, but the width will still be returned * kernPercentage determines the space between each letter. If it's 0, there will be no space between letters. * Otherwise, there will be space between each letter. The value is a fraction of the width of a blank space. */ private int drawKernedText(Canvas canvas, String text, float xOffset, float yOffset, Paint paint, float kernPercentage) { Rect textRect = new Rect(); int width = 0; int space = Math.round(paint.measureText(" ") * kernPercentage); for (int i = 0; i < text.length(); i++) { if (canvas != null) { canvas.drawText(String.valueOf(text.charAt(i)), xOffset, yOffset, paint); } int charWidth; if (text.charAt(i) == ' ') { charWidth = Math.round(paint.measureText(String.valueOf(text.charAt(i)))) + space; } else { paint.getTextBounds(text, i, i + 1, textRect); charWidth = textRect.width() + space; } xOffset += charWidth; width += charWidth; } return width; } 

Hay una pequeña edición de la respuesta de @Pedro Barros. Es útil si usa SpannableString para establecerlo, por ejemplo, si desea hacer diferentes colores de algunos caracteres:

 private void applySpacing() { SpannableString finalText; if (!(originalText instanceof SpannableString)) { if (this.originalText == null) return; StringBuilder builder = new StringBuilder(); for (int i = 0; i < originalText.length(); i++) { builder.append(originalText.charAt(i)); if (i + 1 < originalText.length()) { builder.append("\u00A0"); } } finalText = new SpannableString(builder.toString()); } else { finalText = (SpannableString) originalText; } for (int i = 1; i < finalText.length(); i += 2) { finalText.setSpan(new ScaleXSpan((spacing + 1) / 10), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } super.setText(finalText, TextView.BufferType.SPANNABLE); } 

Quería usar la respuesta de @PedroBarros, pero al definir cuál debe ser el espaciado en píxeles.

Aquí está mi edición del método applySpacing:

 private void applySpacing() { if (this == null || this.originalText == null) return; Paint testPaint = new Paint(); testPaint.set(this.getPaint()); float spaceOriginalSize = testPaint.measureText("\u00A0"); float spaceScaleXFactor = ( spaceOriginalSize > 0 ? spacing/spaceOriginalSize : 1); StringBuilder builder = new StringBuilder(); for(int i = 0; i < originalText.length(); i++) { builder.append(originalText.charAt(i)); if(i+1 < originalText.length()) { builder.append("\u00A0"); } } SpannableString finalText = new SpannableString(builder.toString()); if(builder.toString().length() > 1) { for(int i = 1; i < builder.toString().length(); i+=2) { finalText.setSpan(new ScaleXSpan(spaceScaleXFactor), i, i+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } super.setText(finalText, BufferType.SPANNABLE); } 

Soy un principiante como desarrollador de Android, no dude en decirme si esto no es bueno.

Una solución más

 public static SpannableStringBuilder getSpacedSpannable(Context context, String text, int dp) { if (text == null) return null; if (dp < 0) throw new RuntimeException("WRONG SPACING " + dp); Canvas canvas = new Canvas(); Drawable drawable = ContextCompat.getDrawable(context, R.drawable.pixel_1dp); Bitmap main = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); canvas.setBitmap(main); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); SpannableStringBuilder builder = new SpannableStringBuilder(); char[] array = text.toCharArray(); Bitmap bitmap = Bitmap.createScaledBitmap(main, dp * main.getWidth(), main.getHeight(), false); for (char ch : array) { builder.append(ch); builder.append(" "); ImageSpan imageSpan = new ImageSpan(context, bitmap); builder.setSpan(imageSpan, builder.length() - 1, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } return builder; } 

Donde pixel_1dp es XML:

      

Para establecer el espaciado usa un código como este:

 textView.setText(getSpacedSpannable(context, textView.getText().toString(), ), TextView.BufferType.SPANNABLE); 

Es difícil ajustar el espacio entre los caracteres cuando está usando TextView. Pero si puede manejar el dibujo usted mismo, debería haber alguna forma de hacerlo.

Mi respuesta a esta pregunta es: use su Span personalizado .

Mi código:

 public class LetterSpacingSpan extends ReplacementSpan { private int letterSpacing = 0; public LetterSpacingSpan spacing(int space) { letterSpacing = space; return this; } @Override public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) { return (int) paint.measureText(text, start, end) + (text.length() - 1) * letterSpacing; } @Override public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) { int length = text.length(); float currentX = x; for (int i = 1; i < length; i++) { canvas.drawText(text, i, i + 1, currentX, y, paint); currentX += paint.measureText(text, i, i + 1) + letterSpacing; } } } 

Explique:

Construir su propio Span puede ayudarlo a lograr un efecto increíble, como hacer un blur TextView, cambiar el fondo o el primer plano de su TextView, incluso hacer alguna animación. Aprendo mucho de esta publicación Span un concepto poderoso .

Debido a que está agregando espacio a cada carácter, entonces deberíamos usar un intervalo de nivel de nivel de personaje, en este caso, ReplacementSpan es la mejor opción. Agregué un método de spacing , así que cuando lo use, puede simplemente pasar el espacio que desea para cada carácter como parámetro.

Al construir su tramo personalizado, debe anular al menos dos métodos, getSize y draw . El método getSize debe devolver el ancho final después de que agreguemos el espacio para toda la secuencia de char, y dentro del bloque de método de draw , puede controlar el Canvas para hacer el dibujo que desee.

Entonces, ¿cómo usamos este LetterSpacingSpan? Es fácil:

Uso:

 TextView textView; Spannable content = new SpannableString("This is the content"); textView.setSpan(new LetterSpacingSpan(), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); textView.setText(content); 

Y eso es.