Reputation: 15738
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:
googleandroiddeveloper@gmai
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.
Upvotes: 9
Views: 2792
Reputation: 15738
@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:
onSizeChanged
instead of onMeasure
for checking and manipulating the text, because there are problems with onMeasure
when using LinearLayout
with layout_weight
.getPaddingLeft()
and getPaddingRight()
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("\n@")) {
// 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 + "\n@" + afterAt);
}
}
}
}
}
Here is a screenshot of EmailTextView
compared to default TextView
:
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.
Upvotes: 6
Reputation: 87064
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 = "\n@";
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);
}
}
}
}
}
Upvotes: 2