How to add image in a TextView text?

JavaAndroidXmlImageTextview

Java Problem Overview


I've searched around on Google and came across this site where I found a question similar to mine in which how to include a image in a TextView text, for example "hello my name is [image]", and the answer was this:

ImageSpan is = new ImageSpan(context, resId);
text.setSpan(is, index, index + strLength, 0);

I would like to know in this code,

  1. What am I supposed to type or do in the context?
  2. Am I supposed to do something to the text.setSpan() like import or reference or leave it text?

If someone can break this down for me that would be much appreciated.

Java Solutions


Solution 1 - Java

Try this ..

	txtview.setCompoundDrawablesWithIntrinsicBounds(
					R.drawable.image, 0, 0, 0);

Also see this.. http://developer.android.com/reference/android/widget/TextView.html

Try this in xml file

    <TextView
        android:id="@+id/txtStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:drawableLeft="@drawable/image"
        android:drawablePadding="5dp"
        android:maxLines="1"
        android:text="@string/name"/>

Solution 2 - Java

com/xyz/customandroid/ TextViewWithImages .java:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.Context;
import android.text.Spannable;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

public class TextViewWithImages extends TextView {

	public TextViewWithImages(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	public TextViewWithImages(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	public TextViewWithImages(Context context) {
		super(context);
	}
	@Override
	public void setText(CharSequence text, BufferType type) {
		Spannable s = getTextWithImages(getContext(), text);
		super.setText(s, BufferType.SPANNABLE);
	}

	private static final Spannable.Factory spannableFactory = Spannable.Factory.getInstance();

	private static boolean addImages(Context context, Spannable spannable) {
		Pattern refImg = Pattern.compile("\\Q[img src=\\E([a-zA-Z0-9_]+?)\\Q/]\\E");
	    boolean hasChanges = false;

	    Matcher matcher = refImg.matcher(spannable);
	while (matcher.find()) {
	    boolean set = true;
	    for (ImageSpan span : spannable.getSpans(matcher.start(), matcher.end(), ImageSpan.class)) {
	        if (spannable.getSpanStart(span) >= matcher.start()
	         && spannable.getSpanEnd(span) <= matcher.end()
	           ) {
	            spannable.removeSpan(span);
	        } else {
	            set = false;
	            break;
	        }
	    }
	    String resname = spannable.subSequence(matcher.start(1), matcher.end(1)).toString().trim();
	    int id = context.getResources().getIdentifier(resname, "drawable", context.getPackageName());
	    if (set) {
	        hasChanges = true;
	        spannable.setSpan(  new ImageSpan(context, id),
				                matcher.start(),
				                matcher.end(),
				                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
				             );
	    }
	}

	    return hasChanges;
	}
	private static Spannable getTextWithImages(Context context, CharSequence text) {
	    Spannable spannable = spannableFactory.newSpannable(text);
	    addImages(context, spannable);
	    return spannable;
	}
}

Use:

in res/layout/mylayout.xml:

            <com.xyz.customandroid.TextViewWithImages
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#FFFFFF00"
                android:text="@string/can_try_again"
                android:textSize="12dip"
                style=...
                />

Note that if you place TextViewWithImages.java in some location other than com/xyz/customandroid/, you also must change the package name, com.xyz.customandroid above.

in res/values/strings.xml:

<string name="can_try_again">Press [img src=ok16/] to accept or [img src=retry16/] to retry</string>

where ok16.png and retry16.png are icons in the res/drawable/ folder

Solution 3 - Java

I tried many different solutions and this for me was the best:

SpannableStringBuilder ssb = new SpannableStringBuilder(" Hello world!");
ssb.setSpan(new ImageSpan(context, R.drawable.image), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
tv_text.setText(ssb, TextView.BufferType.SPANNABLE);

This code uses a minimum of memory.

Solution 4 - Java

fun TextView.addImage(atText: String, @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)
    val start = text.indexOf(atText)
    ssb.setSpan(VerticalImageSpan(drawable), start, start + atText.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
    this.setText(ssb, TextView.BufferType.SPANNABLE)
}

VerticalImageSpan class from great answer https://stackoverflow.com/a/38788432/5381331

Using

val textView = findViewById<TextView>(R.id.textview)
textView.setText("Send an [email-icon] to example@email.com.")
textView.addImage("[email-icon]", R.drawable.ic_email,
        resources.getDimensionPixelOffset(R.dimen.dp_30),
        resources.getDimensionPixelOffset(R.dimen.dp_30))

Result

https://i.stack.imgur.com/vRYYI.png" width="350"/>

Note
Why VerticalImageSpan class?
ImageSpan.ALIGN_CENTER attribute requires API 29.
Also, after the test, I see that ImageSpan.ALIGN_CENTER only work if the image smaller than the text, if the image bigger than the text then only image is in center, text not center, it align on bottom of image

Solution 5 - Java

This answer is based on this excellent answer by 18446744073709551615. Their solution, though helpful, does not size the image icon with the surrounding text. It also doesn't set the icon colour to that of the surrounding text.

The solution below takes a white, square icon and makes it fit the size and colour of the surrounding text.

public class TextViewWithImages extends TextView {

    private static final String DRAWABLE = "drawable";
    /**
     * Regex pattern that looks for embedded images of the format: [img src=imageName/]
     */
    public static final String PATTERN = "\\Q[img src=\\E([a-zA-Z0-9_]+?)\\Q/]\\E";

    public TextViewWithImages(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

    public TextViewWithImages(Context context) {
        super(context);
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        final Spannable spannable = getTextWithImages(getContext(), text, getLineHeight(), getCurrentTextColor());
        super.setText(spannable, BufferType.SPANNABLE);
    }

    private static Spannable getTextWithImages(Context context, CharSequence text, int lineHeight, int colour) {
        final Spannable spannable = Spannable.Factory.getInstance().newSpannable(text);
        addImages(context, spannable, lineHeight, colour);
        return spannable;
    }

    private static boolean addImages(Context context, Spannable spannable, int lineHeight, int colour) {
        final Pattern refImg = Pattern.compile(PATTERN);
        boolean hasChanges = false;

        final Matcher matcher = refImg.matcher(spannable);
        while (matcher.find()) {
            boolean set = true;
            for (ImageSpan span : spannable.getSpans(matcher.start(), matcher.end(), ImageSpan.class)) {
                if (spannable.getSpanStart(span) >= matcher.start()
                        && spannable.getSpanEnd(span) <= matcher.end()) {
                    spannable.removeSpan(span);
                } else {
                    set = false;
                    break;
                }
            }
            final String resName = spannable.subSequence(matcher.start(1), matcher.end(1)).toString().trim();
            final int id = context.getResources().getIdentifier(resName, DRAWABLE, context.getPackageName());
            if (set) {
                hasChanges = true;
                spannable.setSpan(makeImageSpan(context, id, lineHeight, colour),
                        matcher.start(),
                        matcher.end(),
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
                );
            }
        }
        return hasChanges;
    }

    /**
     * Create an ImageSpan for the given icon drawable. This also sets the image size and colour.
     * Works best with a white, square icon because of the colouring and resizing.
     *
     * @param context       The Android Context.
     * @param drawableResId A drawable resource Id.
     * @param size          The desired size (i.e. width and height) of the image icon in pixels.
     *                      Use the lineHeight of the TextView to make the image inline with the
     *                      surrounding text.
     * @param colour        The colour (careful: NOT a resource Id) to apply to the image.
     * @return An ImageSpan, aligned with the bottom of the text.
     */
    private static ImageSpan makeImageSpan(Context context, int drawableResId, int size, int colour) {
        final Drawable drawable = context.getResources().getDrawable(drawableResId);
        drawable.mutate();
        drawable.setColorFilter(colour, PorterDuff.Mode.MULTIPLY);
        drawable.setBounds(0, 0, size, size);
        return new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
    }

}

How to use:

Simply embed references to the desired icons in the text. It doesn't matter whether the text is set programatically through textView.setText(R.string.string_resource); or if it's set in xml.

To embed a drawable icon named example.png, include the following string in the text: [img src=example/].

For example, a string resource might look like this:

<string name="string_resource">This [img src=example/] is an icon.</string>

Solution 6 - Java

This is partly based on this earlier answer above by @A Boschman. In that solution, I found that the input size of the image greatly affected the ability of makeImageSpan() to properly center-align the image. Additionally, I found that the solution affected text spacing by creating unnecessary line spacing.

I found BaseImageSpan (from Facebook's Fresco library) to do the job particularly well:

 /**
 * Create an ImageSpan for the given icon drawable. This also sets the image size. Works best
 * with a square icon because of the sizing
 *
 * @param context       The Android Context.
 * @param drawableResId A drawable resource Id.
 * @param size          The desired size (i.e. width and height) of the image icon in pixels.
 *                      Use the lineHeight of the TextView to make the image inline with the
 *                      surrounding text.
 * @return An ImageSpan, aligned with the bottom of the text.
 */
private static BetterImageSpan makeImageSpan(Context context, int drawableResId, int size) {
    final Drawable drawable = context.getResources().getDrawable(drawableResId);
    drawable.mutate();
    drawable.setBounds(0, 0, size, size);
    return new BetterImageSpan(drawable, BetterImageSpan.ALIGN_CENTER);
}

Then supply your betterImageSpan instance to spannable.setSpan() as usual

Solution 7 - Java

Let's say it is our TextView

<TextView
    android:id="@+id/title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:drawablePadding="4dp"
    android:drawableRight="@drawable/edit"
    android:text="Hello world"
    android:textSize="18dp" />

Now we can add any one of the following lines to it as per our requirement

android:drawableLeft="@drawable/filename"
android:drawableRight="@drawable/filename"
android:drawableTop="@drawable/filename"
android:drawableBottom="@drawable/filename"

Solution 8 - Java

This might Help You

  SpannableStringBuilder ssBuilder;

        ssBuilder = new SpannableStringBuilder(" ");
        // working code ImageSpan image = new ImageSpan(textView.getContext(), R.drawable.image);
        Drawable image = ContextCompat.getDrawable(textView.getContext(), R.drawable.image);
        float scale = textView.getContext().getResources().getDisplayMetrics().density;
        int width = (int) (12 * scale + 0.5f);
        int height = (int) (18 * scale + 0.5f);
        image.setBounds(0, 0, width, height);
        ImageSpan imageSpan = new ImageSpan(image, ImageSpan.ALIGN_BASELINE);
        ssBuilder.setSpan(
                imageSpan, // Span to add
                0, // Start of the span (inclusive)
                1, // End of the span (exclusive)
                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);// Do not extend the span when text add later

        ssBuilder.append(" " + text);
        ssBuilder = new SpannableStringBuilder(text);
        textView.setText(ssBuilder);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCranosaurView Question on Stackoverflow
Solution 1 - JavaUmesh LakhaniView Answer on Stackoverflow
Solution 2 - Java18446744073709551615View Answer on Stackoverflow
Solution 3 - JavaPavel KataykinView Answer on Stackoverflow
Solution 4 - JavaLinhView Answer on Stackoverflow
Solution 5 - JavaReinstate MonicaView Answer on Stackoverflow
Solution 6 - Javakip2View Answer on Stackoverflow
Solution 7 - JavaPartha PaulView Answer on Stackoverflow
Solution 8 - JavaHimanshu sainiView Answer on Stackoverflow