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)
}
Related
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:
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.
How can I get rid of unwanted space over ImageSpan that appears if I add text to EditText?
In EditText, a single ImageSpan takes the whole height, but if I add any text, unwanted space appears over the image, and height of the EditText changes. Can I avoid that?
It appears that the height of the EditText is calculated as the difference between the minimum top of the text and the maximum bottom. Direction in the font metrics is downwards, and 0 is the base line of the text.
In the left screenshot, as set by DynamicDrawableSpan.getSize(), bottom is 0, and top is -250, which is the image height. That gives us the height of 250.
In the right screenshot, the additional text sets the bottom to 15. Top is still at -250. The height becomes 265 pixels. ImageSpan still draws from the bottom, so you get this weird space.
I'm not sure if this is the best solution but this works for my use case:
class FullHeightImageSpan(
val context: Context,
val bitmap: Bitmap
) : ImageSpan(context, bitmap) {
override fun getSize(paint: Paint, text: CharSequence?,
start: Int, end: Int,
fm: Paint.FontMetricsInt?): Int {
val oldBottom = fm?.bottom
val result = super.getSize(paint, text, start, end, fm)
fm?.apply {
top += oldBottom!!
ascent = top
bottom = oldBottom
}
return result
}
}
Here I'm simply preserving the bottom of the text instead of setting it to 0.
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 am trying to better understand how layer drawables work within a buttons drawable(s).
I am trying to draw 2 simple colored boxes, one without insets so that it fills the entire button drawable area. And one with some inset.
ColorDrawable background1 = new ColorDrawable(Color.BLUE);
ColorDrawable background2 = new ColorDrawable(Color.GREEN);
Drawable[] drawables = new Drawable[] {
background1,
background2
};
LayerDrawable ld = new LayerDrawable(drawables);
ld.setLayerInset(0, 0, 0, 0, 0 ); // no inset on white box
ld.setLayerInset(1, 8, 8, 8, 8 ); // 8 inset on all side on green box
// set the left drawable on the button
button.setCompoundDrawablesWithIntrinsicBounds(ld, null, null, null);
However that doesn't work at all. The first problem is that the boxes are not filling any area. Is that because a buttons drawables(s) don't have a predefined size? If that is the case I tried to set the bound manually on the boxes, but didn't have much luck either.
Can anyone help me understand what I am doing wrong?
Create a Specific Design in Drawable and call in background button in xml
The problem right now with ColorDrawable is that getIntrinsicWidth()/getIntrinsicHeight() that determine the bounds of the drawable are by default -1, thus it doesn't render on the screen. What would help in your case is to extend ColorDrawable and override the height and width from the constructor.
Here's a sample:
class CustomDrawable(color: Int, val h: Int, val w: Int): ColorDrawable(color) {
override fun getIntrinsicHeight(): Int {
return h
}
override fun getIntrinsicWidth(): Int {
return w
}
}
and then you can instantiate this instead of ColorDrawable like so
val background1 = CustomDrawable(Color.BLUE, dimensionInPx , dimensionInPx)
val background2 = CustomDrawable(Color.GREEN, dimensionInPx, dimensionInPx)
be sure to convert dp to px before passing these values
Hope this helps.