23

This is what happens in the preview and on device: Text bug

TextView is nothing special, it just loads the custom font:

public class TestTextView extends AppCompatTextView {

    public TestTextView(Context context) {
        super(context);

        init(context);
    }

    public TestTextView(Context context, AttributeSet attrs) {
        super(context, attrs);

        init(context);
    }

    public TestTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        init(context);
    }

    void init(Context context) {

        Typeface t = Typeface.createFromAsset(context.getAssets(), "fonts/daisy.ttf");

        setTypeface(t);
    }
}

Layout is also very basic, but just in case:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/material_red200"
    android:orientation="vertical">    

    <*custompackage* .TestTextView
        android:gravity="left"
        android:padding="0dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="just some text for testing"
        android:textColor="@color/material_black"
        android:textSize="100dp" />

</LinearLayout>

As you can see, the left parts, like 'j' and 'f' are cut off.

Setting the padding or margin did not work.

This font fits into it's frame when using from other programs.

Thanks in advance.

Edit: What @play_err_ mentioned is not a solution in my case.

  • I am using in the final version a textview that resizes automatically, so adding spaces would be terribly difficult.
  • I need an explanation why other programs (eg photoshop, after effects...) can calculate a proper bounding box and android cannot
  • I am also loading different fonts dynamically and I do not want to create an

    if(badfont)
         addSpaces()
    
5
  • try to remove android:padding="0dp" Commented May 19, 2017 at 22:21
  • @AlexanderTumanin this does not have any effect on the outcome.
    – andras
    Commented May 24, 2017 at 11:39
  • Try adding a white space after the last character or set a fixed width for the TestTextView.
    – Pro Mode
    Commented May 24, 2017 at 11:40
  • Is it possible to share the font file? And is any custom text style and border is applied?
    – Amit Kumar
    Commented May 27, 2017 at 14:52
  • @AmitKumar This is the font I am using: 1001fonts.com/daisy-script-font.html
    – andras
    Commented May 27, 2017 at 15:14

7 Answers 7

14

This answer has led me to the right path: https://stackoverflow.com/a/28625166/4420543

So, the solution is to create a custom Textview and override the onDraw method:

    @Override
    protected void onDraw(Canvas canvas) {
        final Paint paint = getPaint();
        final int color = paint.getColor();
        // Draw what you have to in transparent
        // This has to be drawn, otherwise getting values from layout throws exceptions
        setTextColor(Color.TRANSPARENT);
        super.onDraw(canvas);
        // setTextColor invalidates the view and causes an endless cycle
        paint.setColor(color);

        System.out.println("Drawing text info:");

        Layout layout = getLayout();
        String text = getText().toString();

        for (int i = 0; i < layout.getLineCount(); i++) {
            final int start = layout.getLineStart(i);
            final int end = layout.getLineEnd(i);

            String line = text.substring(start, end);

            System.out.println("Line:\t" + line);

            final float left = layout.getLineLeft(i);
            final int baseLine = layout.getLineBaseline(i);

            canvas.drawText(line,
                    left + getTotalPaddingLeft(),
                    // The text will not be clipped anymore
                    // You can add a padding here too, faster than string string concatenation
                    baseLine + getTotalPaddingTop(),
                    getPaint());
        }
    }
6
  • @lasnow sure it does not. This is overriding the entire drawing mechanism and then just uses the data to provide the new rendering. You have to render the cursor for yourself.
    – andras
    Commented May 9, 2019 at 9:05
  • yep, by removing the line setTextColor(Color.TRANSPARENT); the cursor and the Text are properly shown. Now it's working, thanks! I also have to add that changing the color programmatically does not work. I had to override the setTextColor and save the color in a property and then instead of retrieving the color from paint.getColor() do that: final int color = this.color != null ? this.color : paint.getColor();
    – lasnow
    Commented May 9, 2019 at 16:32
  • Then you are rendering everything on top with duplicated text that is slightly offset from the original. If that's not the case, then you don't need this entire override solution anyway.
    – andras
    Commented May 9, 2019 at 16:34
  • The only I know is that the italics font weren't working and now with your solution and setting the color to transparent is working (only tested with EditText)
    – lasnow
    Commented May 9, 2019 at 16:37
  • I am using this, the cutting issue is resolved, but now 2 texts are shown in the same textView Commented Jul 29, 2019 at 11:50
9

Reworked @Dmitry Kopytov solution:

  • in Kotlin
  • recycle the old bitmap
  • added documentation
  • fall back on default TextView rendering if the bitmap cannot be created (not enough memory)

Code:

/**
 * This TextView is able to draw text on the padding area.
 * It's mainly used to support italic texts in custom fonts that can go out of bounds.
 * In this case, you've to set an horizontal padding (or just end padding).
 *
 * This implementation is doing a render-to-texture procedure, as such it consumes more RAM than a standard TextView,
 * it uses an additional bitmap of the size of the view.
 */
class TextViewNoClipping(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) {
    private class NonClippableCanvas(@NonNull val bitmap: Bitmap) : Canvas(bitmap) {
        override fun clipRect(left: Float, top: Float, right: Float, bottom: Float): Boolean {
            return true
        }
    }

    private var rttCanvas: NonClippableCanvas? = null

    override fun onSizeChanged(width: Int, height: Int,
                               oldwidth: Int, oldheight: Int) {
        if ((width != oldwidth || height != oldheight) && width > 0 && height > 0) {
            rttCanvas?.bitmap?.recycle()
            try {
                Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)?.let {
                    rttCanvas = NonClippableCanvas(it)
                }
            } catch (t: Throwable) {
                // If for some reasons the bitmap cannot be created, we fall back on default rendering (potentially cropping the text).
                rttCanvas?.bitmap?.recycle()
                rttCanvas = null
            }
        }

        super.onSizeChanged(width, height, oldwidth, oldheight)
    }

    override fun onDraw(canvas: Canvas) {
        rttCanvas?.let {
            // Clear the RTT canvas from the previous font.
            it.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

            // Draw on the RTT canvas (-> bitmap) that will use clipping on the NonClippableCanvas, resulting in no-clipping
            super.onDraw(it)

            // Finally draw the bitmap that contains the rendered text (no clipping used here, will display on top of padding)
            canvas.drawBitmap(it.bitmap, 0f, 0f, null)

        } ?: super.onDraw(canvas) // If rtt is not available, use default rendering process
    }
}
0
8

I encountered the same problem when I used some fonts in EditText.

My first attempt was to use padding. Size of view increased but text is still cropped.

enter image description here

Then I looked at the source code TextView. In method onDraw method Canvas.clipRect is called to perform this crop.

enter image description here

My solution to bypass cropping when use padding :

1) Сreate custom class inherited from Canvas and override method clipRect

public class NonClippableCanvas extends Canvas {

    public NonClippableCanvas(@NonNull Bitmap bitmap) {
        super(bitmap);
    }

    @Override
    public boolean clipRect(float left, float top, float right, float bottom) {
        return true;
    }
}

2) Create custom TextView and override methods onSizeChanged and onDraw.

In the method onSizeChanged create bitmap and canvas.

In the method onDraw draw on bitmap by passing our custom Canvas to method super.onDraw. Next, draw this bitmap on the target canvas.

public class CustomTextView extends AppCompatTextView {
    private Bitmap _bitmap;
    private NonClippableCanvas _canvas;

    @Override
    protected void onSizeChanged(final int width, final int height,
                             final int oldwidth, final int oldheight) {
        if (width != oldwidth || height != oldheight) {
            _bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            _canvas = new NonClippableCanvas(_bitmap);
        }

        super.onSizeChanged(width, height, oldwidth, oldheight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        _canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

        super.onDraw(_canvas);

        canvas.drawBitmap(_bitmap, 0, 0, null);
    }
}

enter image description here

3
  • When I use this it works but if I need to scale the TextView it shows blurry.
    – lasnow
    Commented May 8, 2019 at 21:52
  • 1
    It's not working in 2023
    – Skullper
    Commented Feb 1, 2023 at 12:43
  • Not working for me, unfortunately Commented Nov 28, 2023 at 10:40
8

I have encountered the same problem and i found a one liner solution for thouse who are not using the TextView.shadowLayer.

this is based on the source code that [Dmitry Kopytov] brought here:

editTextOrTextView.setShadowLayer(editTextOrTextView.textSize, 0f, 0f, Color.TRANSPARENT)

that's it, now the canvas.clipRect in TextView.onDraw() won't cut off the curly font sides.

1
  • 1
    This can incur a serious performance penalty if you have a lot of text in the view.
    – user11981435
    Commented Nov 8, 2020 at 18:27
2

A workaround is to add a space before typing. It will save you a lot of coding but will result in a "padding" to the left.

android:text=" text after a space"

3
  • 1
    That is not a good idea. This solution might only work for a single-line text. If there are multiple lines, you have to calculate which text goes into which line and add a space in front (plus if the current line's content gets to the next line after spacing, or if a word is too long for a single line). Additionally, one space might not be enough. So this only works in a very specific case.
    – andras
    Commented Mar 4, 2019 at 8:23
  • 1
    Another solution is to increase the textbox width by a few dps and place the text in the center. This can be done in the activity Java file.
    – idk
    Commented Mar 5, 2019 at 9:31
  • This hint was useful for my case. I needed to output 1-3 symbols and they were clipped from the sides. I didn't wanted to do anything complicated and universal with overriding classes. Adding spaces resolved my problem.
    – mspnr
    Commented Mar 30, 2021 at 16:11
0

replace TextView.BufferType.SPANNABLE with TextView.BufferType.NORMAL

-1

What if you wrap it in another layout and add padding to that? For example something like this:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="24dp">
        <*custompackage* .TestTextView
        android:gravity="left"
        android:padding="0dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="just some text for testing"
        android:textColor="@color/material_black"
        android:textSize="100dp" />
</RelativeLayout>

Not having your font and other themes etc I've just tried it with the cursive font for example and on my machine it would look like this. screenshot

Update: Looks like you're not the only one to have had this issue and the other answers here and here both unfortunately relate to adding extra spaces.

I've created a bug ticket here since it looks like a bug to me.

5
  • I think its still getting a cut off. Look at the first character "j"
    – Pro Mode
    Commented May 24, 2017 at 12:18
  • Oh, my bad. Didn't actually notice that initially. I just assumed that was the style of the font.
    – Kai
    Commented May 24, 2017 at 12:49
  • Padding caused the textview to be smaller (as expected), did not have any effect on the cut.
    – andras
    Commented May 24, 2017 at 13:41
  • 1
    Yea, done a bit of googling seems like the only "fixes" people have come up with to the issue are to add extra spaces. I've created a bug ticket with google as mentioned in my answer above (Not sure if they'll respond but worth a try).
    – Kai
    Commented May 25, 2017 at 1:27
  • @Kai Bug ticket is a great idea. I am also curious if they will respond.
    – andras
    Commented May 27, 2017 at 15:17

Not the answer you're looking for? Browse other questions tagged or ask your own question.