Cómo ajustar el kerning de texto en Android TextView?

¿Hay una manera de ajustar el espaciado entre los caracteres en un Android TextView ? Creo que esto se llama típicamente "kerning".

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

AFAIK, no puede ajustar kerning en TextView . Es posible que pueda ajustar el kerning si dibuja el texto en el Canvas usando las API gráficas 2D.

Construí 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 de la cadena y con SpannedString cambia el TextScaleX de los espacios, permitiendo 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; } } 

Usá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 sencilla de aplicar el kerning a cualquier cadena (técnicamente, CharSequence ) sin usar 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; } 

La única manera que encontré para ajustar el kerning, es crear una fuente personalizada en la que el avance de glifo se altera.

Aquí está mi solución, que añade espaciado uniforme (en píxeles) entre cada carácter. Este intervalo supone que todo el texto está en una sola línea. Esto básicamente implementa lo que sugiere el comando @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; } } } 

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

Esta respuesta puede ser útil para alguien que quiere dibujar texto con kerning en una lona, ​​usando drawText (esto no es sobre texto en un TextView).

Desde Lollipop, el método setLetterSpacing está disponible en Paint. Si el SDK es LOLLIPOP y está activado, se utiliza 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); } /** * Programatically 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 utiliza SpannableString para establecerlo, por ejemplo, si desea crear 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 utilizar @PedroBarros respuesta, pero al definir lo que el espaciado debe ser en píxeles.

Aquí está mi edición al 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, por favor no dude en hacérmelo saber 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:

 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@android:color/transparent"/> <size android:height="1dp" android:width="1dp"/> </shape> 

Para establecer el espacio utilice un código como este:

 textView.setText(getSpacedSpannable(context, textView.getText().toString(), <Your spacing DP>), TextView.BufferType.SPANNABLE); 
  • Android: No se puede cambiar el texto aparece en AlertDialog
  • Cómo eliminar el margen superior de TextView?
  • Texto con formas en recurso dibujable
  • Alineación de 2 columnas en una vista de texto
  • Selección de texto en TextView e invocación de menú flotante en un clic largo
  • No se puede establecer la sombra de TextView mediante programación
  • Android ¿Cómo enviar texto e imágenes o cualquier objeto con intención?
  • Cómo reemplazar elipses con fade / fading edge en textview en Android sdk?
  • ¿Cómo crear un efecto de marquesina para un texto de menor longitud que no exceda el tamaño de la pantalla en Android?
  • ¿Cómo doy espacio entre texto en textview?
  • Cómo agregar una ruptura de línea en un Android TextView?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.