简体   繁体   English

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

[英]How to adjust text kerning in Android TextView?

Is there a way to adjust the spacing between characters in an Android TextView ? 有没有办法调整Android TextView字符之间的间距? I believe this is typically called "kerning". 我相信这通常被称为“字距调整”。

I'm aware of the android:textScaleX attribute, but that compresses the characters along with the spacing. 我知道android:textScaleX属性,但它会压缩字符和间距。

I built a custom class that extends TextView and adds a method "setSpacing". 我构建了一个扩展TextView的自定义类,并添加了一个方法“setSpacing”。 The workaround is similar to what @Noah said. 解决方法类似于@Noah所说的。 The method adds a space between each letter of the String and with SpannedString changes the TextScaleX of the spaces, allowing positive and negative spacing. 该方法在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);

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

AFAIK, you cannot adjust kerning in TextView . AFAIK,您无法在TextView调整字距调整。 You may be able to adjust kerning if you draw the text on the Canvas yourself using the 2D graphics APIs. 如果您使用2D图形API自己在Canvas上绘制文本,则可以调整字距。

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

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

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. 从Lollipop开始,方法setLetterSpacing在Paint上可用。 If the SDK is LOLLIPOP and on, setLetterSpacing is used. 如果SDK是LOLLIPOP并且打开,则使用setLetterSpacing。 Otherwise, a method is invoked that does something similar to @dgmltn's suggestion above: 否则,调用一个类似于@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;
}

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

It's difficult to adjust spacing between characters, when you are using TextView. 在使用TextView时,很难调整字符间的间距。 But if you can handle the drawing yourself, there should be some way to do that. 但是如果你自己可以处理绘图,那么应该有一些方法可以做到这一点。

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 Span can help you achieve many amazing effect, like make a blur TextView, change the background or foreground for your TextView, even make some animation. 构建自己的Span可以帮助您实现许多惊人的效果,比如模糊TextView,更改TextView的背景或前景,甚至制作一些动画。 I learn a lot from this post Span a powerful concept . 我学到了很多,从这个帖子跨度一个强有力的概念

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. 因为要为每个字符添加间距,所以我们应该使用字符级别的基础跨度,在这种情况下,ReplacementSpan是最佳选择。 I add a spacing method, so when using it, you can simply pass the space you want for each character as parameter. 我添加了一个spacing方法,因此在使用它时,您可以简单地将每个字符所需的空间作为参数传递。

When building your custom span, you need to override at least two method, getSize and draw . 构建自定义范围时,需要至少覆盖​​两个方法getSizedraw The getSize method should return the final width after we add the spacing for the whole charsequence, and inside the draw method block, you can control the Canvas to do the drawing you want. 在我们为整个charsequence添加间距之后, getSize方法应返回最终宽度,并且在draw方法块内,您可以控制Canvas来执行所需的绘制。

So how we use this LetterSpacingSpan? 那么我们如何使用这个LetterSpacingSpan? It's easy: 这很容易:

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. 就是这样。

There's a small edit of @Pedro Barros answer. @Pedro Barros答案有一个小编辑。 It is useful if you use SpannableString to set it, eg if you want to make different colors of some characters: 如果您使用SpannableString来设置它是很有用的,例如,如果您想要为某些字符制作不同的颜色:

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

I wanted to use @PedroBarros answer, but by defining what the spacing should be in pixel. 我想使用@PedroBarros答案,但是通过定义像素中的间距应该是什么。

Here's my edit to the applySpacing method : 这是我对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);
}

I'm a beginner as an Android developer, please feel free to let me know if this is not good ! 我是Android开发人员的初学者,如果不好,请随时告诉我!

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

Since Android 21, you can use set the letterSpacing attribute. 从Android 21开始,您可以使用设置letterSpacing属性。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM