¿Cómo ajustar el interletraje de texto en Android TextView?
spacing kerning (10)
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.
¿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.
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;
}
}
}
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);
Esta respuesta puede ser útil para alguien que quiere dibujar texto con interletraje en un lienzo, 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);
}
/**
* 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 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);
}
La única forma que encontré para ajustar el interletraje es crear una fuente personalizada en la que se altera el avance del glifo.
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.
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;
}
También puede intentar usar un SpannedString pero necesitaría analizarlo y cambiar el espaciado entre caracteres para cada una de las palabras
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 espaciado usa un código como este:
textView.setText(getSpacedSpannable(context, textView.getText().toString(), <Your spacing DP>), TextView.BufferType.SPANNABLE);