如何在 Android TextView 中调整文本字距?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1640659/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 03:23:55  来源:igfitidea点击:

How to adjust text kerning in Android TextView?

androidtextviewspacingkerning

提问by emmby

Is there a way to adjust the spacing between characters in an Android TextView? I believe this is typically called "kerning".

有没有办法调整 Android 中字符之间的间距TextView?我相信这通常被称为“字距调整”。

I'm aware of the android:textScaleXattribute, but that compresses the characters along with the spacing.

我知道这个android:textScaleX属性,但这会压缩字符和间距。

采纳答案by CommonsWare

AFAIK, you cannot adjust kerning in TextView. You may be able to adjust kerning if you draw the text on the Canvasyourself using the 2D graphics APIs.

AFAIK,您不能在TextView. 如果您Canvas使用 2D 图形 API在自己身上绘制文本,您可能能够调整字距调整。

回答by Pedro Barros

I built a custom class that extends TextView and adds a method "setSpacing". The workaround is similar to what @Noah said. The method adds a space between each letter of the String and with SpannedStringchanges the TextScaleX of the spaces, allowing positive and negative spacing.

我构建了一个扩展 TextView 的自定义类并添加了一个方法“setSpacing”。解决方法类似于@Noah所说的。该方法在 String 的每个字母之间添加一个空格,并使用SpannedString更改空格的 TextScaleX,允许正负间距。

Hope that helps someone ^^

希望对某人有帮助^^

/**
 * 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;
    }
}

Using it:

使用它:

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);

回答by Takhion

If anyone is looking for a simple way to apply the kerning to any string (technically, CharSequence) without using a TextView:

如果有人正在寻找一种简单的方法来将字距调整应用于任何字符串(从技术上讲,CharSequence)而不使用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;
}

回答by dgmltn

Here's my solution, which adds uniform spacing (in pixels) between each character. This span assumes all text is in a single line. This basically implements what @commonsWare suggests.

这是我的解决方案,它在每个字符之间添加了统一的间距(以像素为单位)。此跨度假定所有文本都在一行中。这基本上实现了@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;
        }
    }
}

回答by mvds

The only way I found to adjust the kerning, is to create a custom font in which the glyph advance is altered.

我发现调整字距调整的唯一方法是创建一个自定义字体,其中字形前进被改变。

回答by Algar

Since Android 21, you can use set the letterSpacingattribute.

从 Android 21 开始,您可以使用 setletterSpacing属性。

<TextView
    android:width="..."
    android:height="..."
    android:letterSpacing="1.3"/>

回答by Anthonyeef

It's difficult to adjust spacing between characters, when you are using TextView. But if you can handle the drawing yourself, there should be some way to do that.

使用 TextView 时,很难调整字符之间的间距。但是,如果您可以自己处理绘图,则应该有某种方法可以做到这一点。

My answer to this question is: use your custom Span.

我对这个问题的回答是: 使用您的自定义 Span

My code:

我的代码:

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;
         }
    }
}

Explain:

解释:

Building your own Spancan help you achieve many amazing effect, like make a blur TextView, change the background or foreground for your TextView, even make some animation. I learn a lot from this post Span a powerful concept.

构建你自己的Span可以帮助你实现许多惊人的效果,比如制作一个模糊的 TextView,改变你的 TextView 的背景或前景,甚至制作一些动画。我从这篇文章中学到了很多跨越一个强大的概念

Because you are adding spacing to each character, so we should use a character level base span, in this case, ReplacementSpan is the best choice. I add a spacingmethod, so when using it, you can simply pass the space you want for each character as parameter.

因为您要为每个字符添加间距,所以我们应该使用字符级别的基本跨度,在这种情况下,ReplacementSpan 是最佳选择。我添加了一个spacing方法,因此在使用它时,您可以简单地将每个字符所需的空格作为参数传递。

When building your custom span, you need to override at least two method, getSizeand draw. The getSizemethod should return the final width after we add the spacing for the whole charsequence, and inside the drawmethod block, you can control the Canvasto do the drawing you want.

在构建自定义跨度时,您需要覆盖至少两个方法,getSizedraw. 该getSize方法应该在我们为整个字符序列添加间距后返回最终宽度,并且在draw方法块内,您可以控制Canvas进行您想要的绘制。

So how we use this LetterSpacingSpan? It's easy:

那么我们如何使用这个 LetterSpacingSpan 呢?这很简单:

Usage:

用法:

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

And that's it.

就是这样。

回答by Noah

You can also try using a SpannedStringbut you would need to parse it and change the character spacing for each of the words

您也可以尝试使用SpannedString但您需要解析它并更改每个单词的字符间距

回答by TER

This answer may be helpful for someone who wants to draw text with kerning on a Canvas, using drawText (this is not about text in a TextView).

这个答案对于想要使用 drawText 在 Canvas 上使用字距调整绘制文本的人可能会有所帮助(这与 TextView 中的文本无关)。

Since Lollipop, the method setLetterSpacing is available on Paint. If the SDK is LOLLIPOP and on, setLetterSpacing is used. Otherwise, a method is invoked that does something similar to @dgmltn's suggestion above:

自 Lollipop 以来,方法 setLetterSpacing 在 Paint 上可用。如果 SDK 是 LOLLIPOP 等,则使用 setLetterSpacing。否则,将调用一个方法,该方法执行类似于上面@dgmltn 的建议的操作:

    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;
}

回答by Aleksandr

One more solution.

另一种解决方案。

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;
    }

Where pixel_1dp is XML:

其中 pixel_1dp 是 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>

To set spacing use code like this:

要设置间距,请使用如下代码:

textView.setText(getSpacedSpannable(context, textView.getText().toString(), <Your spacing DP>), TextView.BufferType.SPANNABLE);