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:
Need to add Quote as a drawable in multiline text like below mentioned screen. Like you can see quote is in a starting of multiline text and next line text is just starting from the below to the quote.
I need to use TextView to achieve this and quote should be a drawable.
If I am using drawableLeft property of TextView Its showing like below mentioned image.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!\nHello World!\nHello World!\nHello World!"
android:drawableStart="#drawable/ic_quote"
android:drawableLeft="#drawable/ic_quote"/>
You can create a SpannableString to add the drawable :
TextView myTextView = (TextView) findViewById(R.id.myTextView);
ImageSpan imageSpan = new ImageSpan(this, R.drawable.ic_quote);
SpannableString spannableString = new SpannableString("*" + myTextView.getText()); // "*" will be replaced by your drawable
int start = 0; // the start index, inclusive
int end = 1; // the end index, exclusive
int flag = 0;
spannableString.setSpan(imageSpan, start, end, flag);
myTextView.setText(spannableString);
And in your layout :
<TextView
android:id="#+id/myTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!\nHello World!\nHello World!\nHello World!"/>
Try using android:gravity="top"
if it didnt work then go with negative margin like this
android:drawablePadding="-20sp"
Another alternative way is to take an ImageView beside TextView inside LinearLayout so you can apply gravity
You can use SpannableStringBuilder.
val spannableText = SpannableStringBuilder("*Your text!").apply {
val imageSpan = ImageSpan(context, R.drawable.your_icon)
setSpan(imageSpan, 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
}
textView.setText(spannableText, TextView.BufferType.SPANNABLE)
The symbol * will be raplaced witn your icon.
In the picture bellow i want the Footnote string to always appear floating to the right edge of the Textview:
I tried the following but it doesn't work:
SpannableStringBuilder builder = new SpannableStringBuilder();
SpannableString item = new SpannableString ("Main text");
builder.append (item);
builder.append (" ");
String footnote = "Footnote";
int len = footnote.length();
SpannableString subitem = new SpannableString (footnote);
subitem.setSpan (new ForegroundColorSpan (color), 0, len, 0);
subitem.setSpan (new AbsoluteSizeSpan (12, true), 0, len, 0);
subitem.setSpan (new AlignmentSpan.Standard (Layout.Alignment.ALIGN_OPPOSITE), 0, len, 0);
builder.append (subitem);
myTextView.setText (builder, TextView.BufferType.SPANNABLE);
It can be achieved by using 2 textviews in frame layout and set the value of layout_gravity as right and bottom of 2nd second textview.
android:layout_gravity="bottom|right"
Example:
<FrameLayout
android:id="#+id/flString"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<TextView
android:id="#+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text "
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="#android:color/black" />
<TextView
android:id="#+id/tvHighlighted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:text="Footnote"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="#0000ff" />
</FrameLayout>
In case of single line, second highlighted word overlaps the text. It can be handled programatically:
/**
* Used to create 2 text view text in single line like :
*
* Text Text Text Text Text Text Text Text Text Text |Footnote|
*
* #param frameLayout FrameLayout
* #param tvTitle TextView
* #param tvHighlighted TextView
* Directly "View" can be used as param type, but for this case only I
* have used dedicated Views and ViewGroup.
*/
public static void manageTextViews(final FrameLayout frameLayout, final TextView tvTitle, final TextView tvHighlighted) {
tvTitle.post(new Runnable() {
#Override
public void run() {
try {
int lineCount = tvTitle.getLineCount();
Rect bounds = new Rect();
tvTitle.getLineBounds(lineCount > 0 ? lineCount - 1 : 0, bounds);
FrameLayout.LayoutParams fllp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
fllp.gravity = (Gravity.BOTTOM | Gravity.RIGHT);
if (frameLayout.getWidth() <= (bounds.width() + tvBuyable.getWidth() + 10)) {
fllp.setMargins(0, tvTitle.getLineHeight() + 5, 0, 0);
} else {
fllp.setMargins(0, 0, 0, 0);
}
tvHighlighted.setLayoutParams(fllp);
} catch (Exception e) {
Log.e(ClassName + ".manageTextViews()", "mTxtTitle.post", e);
}
}
});
}
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
}
Maybe it's a stupid question, but I cant find a way to inser small image at the end of second line of TextView. Image is always appears to the right of whole textview, not the line end.
I want to insert image like this:
TextTextTextTextTextTextTextTextTextText
TextTextTextText. <ImageView>
What I am getting is:
TextTextTextTextTextTextTextTextTextText <ImageView>
TextTextTextText.
I hope there is way to do it.
Src:
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TESTESTESTESTESTESTESTESTESTESTESTESTES"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#FFFFFF" />
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/tv"
android:src="#drawable/icon" />
</RelativeLayout>
create an ImageSpan and add it to your textview. This way, you can put your image where you want in the text
Example -
ImageSpan imagespan = new ImageSpan(appContext, ressource);
text.setSpan(imagespan, i, i+ strLength, 0); //text is an object of TextView
One best way to complete it is as the following...
ImageGetter getter = new ImageGetter(){
public Drawable getDrawable(String source){ // source is the resource name
Drawable d = null;
Integer id = new Integer(0);
id = getApplicationContext().getResources().getIdentifier(source, "drawable", "com.sampleproject");
d = getApplicationContext().getResources().getDrawable(id);
if (d != null)
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d;
}
};
String imgString = this.getResources().getString(R.string.text_string) + " <img src=\"img_drawable_image\"/>";
((TextView)findViewById(R.id.textview_id)).setText(
Html.fromHtml(imgString, getter, null));
You can add the image to end of the string without using imageview like below;
fun addImageToEndOfTheString(text: String, drawableResourceId : Int ,context: Context) : SpannableStringBuilder {
val drawable = ContextCompat.getDrawable(context, drawableResourceId)!!
drawable.setBounds(14, 0, 64, 50)
val rocketImageSpan = ImageSpan(drawable, ImageSpan.ALIGN_BASELINE)
val ssBuilder = SpannableStringBuilder(text)
ssBuilder.setSpan(
rocketImageSpan,
text.length - 1,
text.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
return ssBuilder
}
and call it like;
textView.text = addImageToEndOfTheString("Your text is here", R.drawable.ic_your_image, context!!)
SpannableString ss = new SpannableString(title);
Drawable d = getResources().getDrawable(R.drawable.gallery_list);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
ss.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
newsTextView.setText(ss);
TextView textView =new TextView(this);
SpannableStringBuilder ssb = new SpannableStringBuilder( "Here's a smiley how are you " );
Bitmap smiley = BitmapFactory.decodeResource( getResources(), R.drawable.movie_add );
ssb.setSpan( new ImageSpan( smiley ), ssb.length()-1, ssb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE );
textView.setText( ssb, BufferType.SPANNABLE );
If you want to insert Drawable at the end of the text, Drawable is hiding the last character of the text to avoid that add another character at the end of the text and start the drawable at that character.
val myText = "Your text"
val span: Spannable = SpannableString(myText+"-")
val android: Drawable = ContextCompat.getDrawable(this, R.drawable.yourDrawable)!!
android.setBounds(0, 0, 30, 30)
val image = ImageSpan(android, ImageSpan.ALIGN_BOTTOM)
span.setSpan(image, span.indexOf("-"), span.indexOf("-")+1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
Kotlin extension on the TextView class
/**
* Creates a StringSpan using the text from the TextView itself and it also adds an ImageSpan at the
* end of the last line of the TextView.
* This basically allows us to set a drawable at the end of the TextView text.
*/
fun TextView.setImageSpanAtTheEnd(context: Context, #DrawableRes drawableRes: Int) {
text = SpannableString("$text ").apply {
val d = ContextCompat.getDrawable(context, drawableRes) ?: throw RuntimeException("Drawable not found!")
d.setBounds(0, 0, d.intrinsicWidth, d.intrinsicHeight)
setSpan(ImageSpan(d, ImageSpan.ALIGN_BASELINE), text.length, text.length+1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE)
}
}
Just call it after you set the text on the TextView and that's it.
It might be not the best solution, but you can try using Html.fromHtml to insert image into your line.
ImageGetter getter = new ImageGetter(){
public Drawable getDrawable(String source){
Drawable d = null;
context.getResources().getDrawable(Integer.parseInt(source));
if (d != null)
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d;
}
};
String imgString = <source string> + " <img src=\"R.drawable.icon\"/>";
((TextView)findViewById(R.id.tv)).setText(
Html.fromHtml(imgString, getter, null));
Thanks to Libin Thomas I created a solution for RatingBar. I used SvgRatingBar with SVG stars.
ImageSpan extends DynamicDrawableSpan, and DynamicDrawableSpan has only one child - ImageSpan. So, to attach another View to the end of the TextView we should get a drawable made from the View. I added a method getDrawable() to SvgRatingBar:
public class SvgRatingBar extends AppCompatRatingBar {
...
private Drawable drawable;
public Drawable getDrawable() {
return drawable;
}
private void init() {
LayerDrawable drawable = (LayerDrawable) createTile(getProgressDrawable(), false);
setProgressDrawable(drawable);
this.drawable = drawable;
}
Create a layout for the rating (layout_rating_bar.xml):
<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.SvgRatingBar xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="11dp"
android:isIndicator="true"
android:minHeight="13dp"
android:numStars="5"
android:progressDrawable="#drawable/rating_bar"
android:rating="3.5"
android:stepSize="0.01" />
Then write this code:
val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val ratingBar: SvgRatingBar = inflater.inflate(R.layout.layout_rating_bar, null, false)
as SvgRatingBar
ratingBar.rating = 3.5f
val drawable = ratingBar.drawable
val ss = SpannableString("dsfjdskljsf dsfjsdkljfslk dsfjlksdjflsdkjf ")
drawable.setBounds(0, 0, 170, 30) // Set width and height of the rating bar here.
val span = ImageSpan(drawable, ImageSpan.ALIGN_BASELINE)
ss.setSpan(span, ss.length - 1, ss.length, Spannable.SPAN_EXCLUSIVE_INCLUSIVE)
textView.setText(ss)