Home » Android » java – Adding a line break at a particular position, only if necessary

java – Adding a line break at a particular position, only if necessary

Posted by: admin June 16, 2020 Leave a comment

Questions:

In my Android layout, I have a TextView that uses half of the available width of the screen. At runtime I set the text to a (long) email address. For instance:

[email protected]

If the text does not fit into one line, Android inserts a line break, which is the desired behavior. However, the position of the line break is before the first character that does not fit in the line. The result can be something like this:

[email protected]
l.com

I think, this is kind of ugly, especially in email addresses. I want the line break to appear right before the @ character:

googleandroiddeveloper
@gmail.com

Of course, i could add a \n in my strings.xml. But then the email address would use two lines in every case, even if it would fit into one line.

I already thought I had found a solution in adding a ZERO WIDTH SPACE (\u200B) to the email address.

<string name="email">googleandroiddeveloper\[email protected]</string>

But other than with standard spaces, Android does not detect the special space character as a breakable space and consequentially does not add a line break at this point.

As I am dealing with a lot of email addresses in multiple places of my application, I am searching for a solution to add a breakable and invisible space before the @ character, so Android wraps the email address if does not fit into one line.

How to&Answers:

@Luksprog’s solution is very good and solves the problem in many cases. However, I modified the class at several points, to make it even better. These are the modifications:

  • I used onSizeChanged instead of onMeasure for checking and manipulating the text, because there are problems with onMeasure when using LinearLayout with layout_weight.
  • I considered the horizontal padding of the text by using getPaddingLeft() and getPaddingRight()
  • At measuring afterAt I replaced position with position + 1, otherwise the resulting email address contains two @.

Code:

public class EmailTextView extends TextView {

    public EmailTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // the width the text can use, that is the total width of the view minus
        // the padding
        int availableWidth = w - getPaddingLeft() - getPaddingRight();
        String text = getText().toString();
        if (text.contains("\[email protected]")) {
            // the text already contains a line break before @
            return;
        }
        // get the position of @ in the string
        int position = -1;
        for (int i = 0; i < text.length(); i++) {
            if (text.charAt(i) == '@') {
                position = i;
                break;
            }
        }
        if (position > 0) {
            final Paint pnt = getPaint();
            // measure width before the @ and after the @
            String beforeAt = text.subSequence(0, position).toString();
            String afterAt = text.subSequence(position + 1, text.length())
                    .toString();
            final float beforeAtSize = pnt.measureText(beforeAt);
            final float afterAtSize = pnt.measureText(afterAt);
            final float atSize = pnt.measureText("@");
            if (beforeAtSize > availableWidth) {
                // the text before the @ is bigger than the width
                // so Android will break it
                return;
            } else {
                if ((beforeAtSize + afterAtSize + atSize) <= availableWidth) {
                // the entire text is smaller than the available width
                    return;
                } else {
                    // insert the line break before the @
                    setText(beforeAt + "\[email protected]" + afterAt);
                }
            }
        }
    }
}

Here is a screenshot of EmailTextView compared to default TextView:

EmailTextView

For all email addresses it works as I expected. The last address is not changed because the text before the @ is already too wide, so the system breaks it before and the thereby the email address is kind of messed up anyway, so there is no need to include another line break.

Answer:

You could have a look at the custom TextView class below(although, probably not very efficient) who should insert(assuming on very few tests) the desired line break in certain cases:

public static class NewLineText extends TextView {

    private static final String CHALLANGE_TEXT = "\[email protected]";

    public NewLineText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        String text = getText().toString();
        if (text.contains(CHALLANGE_TEXT)) {
            return;
        }
        int position = -1;
        for (int i = 0; i < text.length(); i++) {
            if (text.charAt(i) == '@') {
                position = i;
                break;
            }
        }
        if (position > 0) {
            final Paint pnt = getPaint();
            String beforeAt = text.subSequence(0, position).toString();
            String afterAt = text.subSequence(position, text.length())
                    .toString();
            final float beforeAtSize = pnt.measureText(beforeAt);
            final float afterAtSize = pnt.measureText(afterAt);
            final float atSize = pnt.measureText("@");
            if (beforeAtSize > getMeasuredWidth()) {
                // return ?! the text before the @ is bigger than the width
                // so Android will break it
                return;
            } else {
                if ((beforeAtSize + afterAtSize + atSize) <= getMeasuredWidth()) {
                    return;
                } else {
                    setText(beforeAt + CHALLANGE_TEXT + afterAt);
                }
            }
        }
    }
}