How to adjust text kerning in Android TextView?

后端 未结 12 1406
醉话见心
醉话见心 2020-11-30 20:20

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

I\'m aware of the

相关标签:
12条回答
  • 2020-11-30 20:49

    Since Android 21, you can use set the letterSpacing attribute.

    <TextView
        android:width="..."
        android:height="..."
        android:letterSpacing="1.3"/>
    
    0 讨论(0)
  • 2020-11-30 20:50

    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.

    My answer to this question is: use your custom 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. 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. I add a spacing method, so when using it, you can simply pass the space you want for each character as parameter.

    When building your custom span, you need to override at least two method, getSize and draw. 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.

    So how we use this 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.

    0 讨论(0)
  • 2020-11-30 20:57

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

    0 讨论(0)
  • 2020-11-30 20:59

    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 SpannedString changes the TextScaleX of the spaces, allowing positive and negative spacing.

    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);
    
    0 讨论(0)
  • 2020-11-30 21:01

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

    0 讨论(0)
  • 2020-11-30 21:05

    If anyone is looking for a simple way to apply the kerning to any string (technically, CharSequence) without using a 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;
    }
    
    0 讨论(0)
提交回复
热议问题