I often need to have some piece of dynamic text in a TextView, with some drawable image at the end of it.
I'm having troubles making it look nice when the text needs to span onto 2 lines. I would want the image to go inline with the text.
This is what it looks like:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="This is my favourite which has a really long piece of string which will wrap"
app:drawableEndCompat="#drawable/ic_baseline_favorite_border_24" />
This is what I want it to look like:
Drawable End in a TextView, or putting an ImageView next to a TextView doesn't work. Is there an easy way to get this image inline with the text as if it is part of the text?
Based on the Answer here, thanks to #Linh. And on the VerticalImageSpan
This is manipulated to add a drawable at the end of the text:
TextView extension function:
fun TextView.addImageAtEnd(#DrawableRes imgSrc: Int, imgWidth: Int, imgHeight: Int) {
val ssb = SpannableStringBuilder(this.text)
val drawable = ContextCompat.getDrawable(this.context, imgSrc) ?: return
drawable.mutate()
drawable.setBounds(
0, 0,
imgWidth,
imgHeight
)
ssb.setSpan(
VerticalImageSpan(drawable),
length() - 1,
length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
this.setText(ssb, TextView.BufferType.SPANNABLE)
}
Usage:
textView2.text = "This is my favourite which has a really long piece of string which will wrap "
textView2.addImageAtEnd(
R.drawable.heart_outline,
resources.getDimensionPixelOffset(R.dimen.dp_30),
resources.getDimensionPixelOffset(R.dimen.dp_30)
)
Result:
Related
I´d like to use icons in the text of a TextView. (Not in front or in the end of it.) The basic idea is a short infobox, telling the user, that these fancy three lines up there are the menu. (Since it is not possible to write "Menu" below them, except I want to handle all the click events myself too. sigh)
So my text should be something like this:
Right now this is just an icon added at the end of the textview, but as you can clearly see, it looks very ugly, and only works with a fixed screen-size. In landscape mode the icon would be at the completely other side of the screen, than the text.
Therefore I am searching for some way to write icons inside of the text.
I was thinking about something like "Um das Menü zu öffnen, tippe auf #drawable/sandwich" in the string resources or similar. (Which obviously doesn´t work like that, sadly.)
Is this possible? Or, if not, is there maybe some secret trick to add a text to the sandwich icon in the action bar at the top, without creating my custom layout?
Around 50% of my users have issues realizing it is a menu, since they are not used to a lot of apps.
Yes, you can do it using SpannableString. See the below example:
val modifiedText = "your-text-here %icon%" // you can use resource string here
val span = SpannableString(modifiedText)
val drawable = ResourcesCompat.getDrawable(resources, R.drawable.your_image, null)
drawable?.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
val image = ImageSpan(drawable, ImageSpan.BOTTOM)
val startIndex = modifiedText.indexOf("%icon%")
//Replace %icon% with drawable
span.setSpan(image, startIndex, startIndex + 6, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
yourTextView.setText(span)
You could use VerticalImageSpan which is an extension to ImageSpan.
And use the following TextView extension function takes in the index in the TextView that you want to place the drawable in; a drawable, the desired image width & height.
fun TextView.addDrawableAt(index: Int, #DrawableRes imgSrc: Int, imgWidth: Int, imgHeight: Int) {
val ssb = SpannableStringBuilder(this.text)
val drawable = ContextCompat.getDrawable(this.context, imgSrc) ?: return
drawable.mutate()
drawable.setBounds(
0, 0,
imgWidth,
imgHeight
)
ssb.setSpan(
VerticalImageSpan(drawable),
index - 1,
index,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
this.setText(ssb, TextView.BufferType.SPANNABLE)
}
I am trying to use the SpannableStringBuilder to implement spannable color to all occurrence of a given character sequence in the sentence, just like:
val spannableStr1 = SpannableString("hello world!")
spannableStr1.setSpan(ForegroundColorSpan(Color.parseColor("#FF0000")),
0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
val spannableBuilder = SpannableStringBuilder()
spannableBuilder.append(spannableStr1)
spannableBuilder.append(spannableStr1)
tvDemo.text = spannableBuilder
and this resulted to:
I need the second occurrence of the characters hel also in red color. So I tried to append spannableStr1 again, but it seems not working.
Is there any way to implement it?
The span on spannableStr1 is set for 0-3. Unfortunately once you add it to the spannable string builder the second time, it's still 0-3. This causes the problem here- you colored the same part red twice.
A better way of doing this is not to add spans to the text. Instead, just append the text and add the spans directly to the SpannableStringBuilder. This means the first span should be (0,3) and the second one should be (index of second h, index of second h + 3)
val spannableStr1 ="hello world!"
val spannableBuilder = SpannableStringBuilder()
spannableBuilder.append(spannableStr1)
spannableBuilder.append(spannableStr1)
spannableBuilder.setSpan(ForegroundColorSpan(Color.parseColor("#FF0000")),
0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
spannableBuilder.setSpan(ForegroundColorSpan(Color.parseColor("#FF0000")),
spannableStr1.length, spannableStr1.length + 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
tvDemo.text = spannableBuilder
In my case, I have a textView with a drawable icon at the start of it(android:drawableStart), when the text of textView is multi-line, the icon goes in the vertical center of textView, but I want the icon to be aligned to the top of the text, how can I do this?
I don't want use any other layouts for doing this
There is api to set the drawable, but first you should set the bounds .
You can use setCompoundDrawablesWithIntrinsicBounds to set the drawable icons.
textview.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getApplicationContext(), R.drawable.icon), null, ContextCompat.getDrawable(getApplicationContext(), R.drawable.icon_two), null);
As according to the interface you can see the position clearly and use accordingly to your need.
public void setCompoundDrawablesWithIntrinsicBounds (int left,
int top,
int right,
int bottom)
It is something different but a quick workaround.
Just use a Checkbox instead of a TextView and use android:button to define your drawable:
<CheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:button="#drawable/..."
android:gravity="top"
android:clickable="false"
android:text=".."/>
The approach I followed is to use SpannableString by setting a Span to the beginning of the string, being the Spain an ImageSpain
fun TextView.setDrawableAtStart(context: Context, textToSet: String?, #DrawableRes drawableRes: Int) {
if (textToSet.isNullOrBlank()) return
text = SpannableString(" $textToSet ").apply {
val drawable = ContextCompat.getDrawable(context, drawableRes) ?: return
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
setSpan(
ImageSpan(drawable, ImageSpan.ALIGN_BASELINE),
0, // Start position
1, // End position
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
I encourage you to read this interesting article about styling a text in Android.
For example, I have a string «One two three four five» to be set in TextView.
If the text occupies only one line, there’s no change, and it’s OK
If text occupies two lines, then:
expected result is: «One two \n three four five» (exactly after
"two")
actual result is: «One two three four \n five» (the line break
can be anywhere)
Also I have a blank TextView in ConstraintLayout with zero width, so I don’t know the width of the textView before I set text.
How can I achieve this?
As far as I know, the width of text can be measured before setting text and divided by the textView's width, but unfortunately it doesn't work for me, as I don't know the width of textview.
Here's my code:
override fun setText(textView: TextView, prefix: String, postfix: String) {
var fullText = "$prefix $postfix"
val lines = getLinesCount(textView, fullText)
if (lines > 1) {
fullText = fullText.replace(postfix, "\n$postfix")
}
textView.text = fullText
}
private fun getLinesCount(textView: TextView, text: String): Int {
val paint = Paint()
paint.textSize = textView.textSize
val rect = Rect()
paint.getTextBounds(text, 0, text.length, rect)
// I'm not sure how to calculate textView actual width
return (ceil(rect.width().toFloat() / textView.width)).toInt()
}
TextView Supports getLineCount(); which return line count after textview is drawn on UI.
int lineCount = textView.getLineCount();
if(lineCount > 2) {
//your line break logic goes here.
} else {
//normal logic
}
See this doc.
You need to use Html.fromHtml() to format XML textview in html format.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
textView.setText(Html.fromHtml("One two <br> three four five", Html.FROM_HTML_MODE_COMPACT));
} else {
textView.setText(Html.fromHtml("One two <br> three four five"));
}
You can also refer to this link
I need to place an image and string in the center of the button. Need to create spaces between them.
a Hello (%1$d)
My code:
String textValue = getResources().getString(R.string.hello_text);
final Spannable buttonLabel = new SpannableString(String.format(textValue, 2));
//buttonLabel.setSpan(new ImageSpan(getApplicationContext(), R.drawable.hello_imagel,
// ImageSpan.ALIGN_BOTTOM), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mButton.setText(buttonLabel);
I need give spaces between the image and the text. I tried giving spaces in the strings.xml but it doesn't display the spaces. After adding the below xml, the image is to the left and the string is in the center. I want the image and string to be in center with few spaces between image and string
xml:
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="#2A2A2A"
android:drawableLeft="#drawable/hello_image1"
android:gravity="center"
If you don't need to show an icon inside a string you can add drawable to your button.
i.e. an icon at the left side of the text:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="#drawable/icon"
android:drawablePadding="20dp">
where #drawable/icon is your icon and 20dp is padding between icon and text
Shure you can do the same from code - take a look at setCompoundDrawables and setCompoundDrawablePadding
sorry to be late, I hope this method helps you, is on kotlin:
fun setLeftIcon(roundButton: RoundButton, iconId: Int) = with(roundButton) {
if (iconId == DEFAULT_IMAGE_ID) return
val currentText = text.toString()
// The spaces are a workaround to add some space between the image and the text
val buttonLabel = SpannableString(" $currentText")
buttonLabel.setSpan(
ImageSpan(context, iconId),
0,
1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
text = buttonLabel
}