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. Commented May 24, 2017 at 11:39
  • Try adding a white space after the last character or set a fixed width for the TestTextView. 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? Commented May 27, 2017 at 14:52
  • @AmitKumar This is the font I am using: 1001fonts.com/daisy-script-font.html 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());
        }
    }
Sign up to request clarification or add additional context in comments.

@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.
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();
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.
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)
I am using this, the cutting issue is resolved, but now 2 texts are shown in the same textView
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
    }
}

Comments

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

When I use this it works but if I need to scale the TextView it shows blurry.
It's not working in 2023
Not working for me, unfortunately
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.

This can incur a serious performance penalty if you have a lot of text in the view.
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"

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

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

Comments

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

I think its still getting a cut off. Look at the first character "j"
Oh, my bad. Didn't actually notice that initially. I just assumed that was the style of the font.
Padding caused the textview to be smaller (as expected), did not have any effect on the cut.
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 Bug ticket is a great idea. I am also curious if they will respond.

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.