How to adjust text kerning in Android TextView?

后端 未结 12 1339
醉话见心
醉话见心 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 21:07

    I wanted to use @PedroBarros answer, but by defining what the spacing should be in pixel.

    Here's my edit to the applySpacing method :

    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 !

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

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

    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:

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

    There's a small edit of @Pedro Barros answer. It is useful if you use SpannableString to set it, e.g. if you want to make different colors of some characters:

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

    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.

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

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

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

    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:

    <?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);
    
    0 讨论(0)
提交回复
热议问题