97

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.

hiergiltdiestfu
  • 2,339
  • 2
  • 25
  • 36
Cranosaur
  • 1,163
  • 1
  • 11
  • 15

9 Answers9

217

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"/>
Kamil Z
  • 181
  • 12
Umesh Lakhani
  • 2,382
  • 1
  • 14
  • 10
  • I got an error "Cannot make a static reference to the non-static method setCompoundDrawablesWithIntrinsicBounds(int, int, int, int) from the type TextView" – Cranosaur Mar 12 '13 at 03:37
  • Thanks Umesh the xml method worked for me.I use the xml layout for my TextViews so I don't know if that makes a difference and maybe that's why it wasn't working in Java. – Cranosaur Mar 12 '13 at 04:06
  • 1
    @Umesh Lakhani: How is it possible to put multiple drawables in text by this approach? – Behnam Aug 27 '14 at 10:38
  • In XML an image is drawn from left, not in center. – CoolMind Aug 15 '16 at 13:36
  • HI @Umesh. How to set some margin to it. `setCompoundDrawablePadding` isn't doing anything – Prabs Aug 23 '16 at 06:57
  • @Prabs, did it get fixed? I want to add image instead of text in edittext..drawableLeft or right is not appropriate in my case.. – Akshatha S R Aug 31 '17 at 12:13
  • @Prabs, Yes.. Tried drawable left, but it is not suitable. I need image instead of text. Or I want to add floating label above the image.. – Akshatha S R Aug 31 '17 at 12:29
81

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

18446744073709551615
  • 16,368
  • 4
  • 94
  • 127
  • When I use `textView.setText(R.string.can_try_again);` it doesn't show the images, its simply shows the plain text `Press [img src=ok16/] to accept or [img src=retry16/] to retry`. Any Help? This is because I want to dynamically load the images and set them in the textView. – Anas Azeem Jun 03 '14 at 10:57
  • @AnasAzeem are you able to show ok16 and retry16 "normally", via ImageView? Did you specify TextViewWithImages in place of TextView? – 18446744073709551615 Jun 04 '14 at 05:40
  • 1
    don't forget to change – Zar E Ahmer Nov 28 '14 at 06:07
  • @Nepster that's if you place it into _src/YourPackageName_ rather than _src/com/xyz/customandroid/_. But you are right in that the package names must match, and it's better to underline that. – 18446744073709551615 Nov 28 '14 at 06:44
  • It is a cool solution but the performance is not very good. When I use this in a viewpager with a very high end phone it will become pretty faltery when scrolling. – Roel Apr 19 '16 at 09:54
  • 1
    its working but i cant set height width of image and also not getting image in center of text – Rajesh Koshti Jul 18 '16 at 09:53
  • @RajeshKoshti to set image width and height, I used to change the image size in a graphical editor. (It was back in 2014 and the Android was 2.x and 4.0) You may try to add transparent space around the image. – 18446744073709551615 Jul 20 '16 at 07:15
  • @RajeshKoshti I posted another answer building on this solution, it sizes the image to the lineHeight of the surrounding text and also sets the colour to that of the text. – Reinstate Monica Aug 16 '16 at 14:21
  • If we bitmap then how we can deal with this ? – Teraiya Mayur Oct 07 '16 at 06:16
  • @TeraiyaMayur Since there _is_ a ImageSpan constructor that takes a bitmap, you can do it. See the line `int id = context.getResources().getIdentifier(...`. You have to either check that _resname_ is a special name (e.g. starts with `*`), or that an error happened in the line `int id = ...`. Anyway, after you find out that this is not a resource name, you can search a static globally visible `HashMap`. And to make it work, you will have to put your bitmap to that HashMap prior to displaying the text. Or you can have your HashMap store `Callable` rather than `Bitmap`. – 18446744073709551615 Oct 10 '16 at 11:08
  • 1
    Its a great class until you package it in a release build. It crashes and I've still not been able to find out why. Even marked this class in the -keep in proguard. Still no luck. Or maybe the images i used are vector thats why maybe it crashed. I dont know. – Udayaditya Barua Jun 06 '17 at 03:14
  • This class works perfectly fine until we create a release apk, if anyone found a solution for that then please help with the solution. – Arpit Jul 21 '17 at 13:52
  • @Arpit, Does your release build crash always? or on some specific devices? I am interested in this implementation, and tried both debug and release build on my device, they both work without crash. – 正宗白布鞋 Aug 25 '17 at 14:15
  • Hi, Sorry i can not recall if it was crashing or images were not showing in my case but yes this issue was persistent with me on all the devices, i had to manually place images in my textview. My test devices had marshmallow running. – Arpit Aug 29 '17 at 16:56
  • What does the `for`loop in the `addImages()` method do? Can't figure out the purpose of it. – fahmy Oct 14 '17 at 10:15
  • @fahmy In theory, there may already be one or more image spans. The loop removes them. – 18446744073709551615 Oct 16 '17 at 13:27
  • @fahmy What is interesting is `set`. If the code finds a span that it cannot remove (because it spans outside of `[img src=.../]`), it sets `set` to false and does not add any new image span -- that it, it does not replace the text like `[img src=ok16/]` with the corresponding image. The code was borrowed from something trying to implement the general case. I am not sure if such conditions could be encountered in Android 2.x or may be encountered now. – 18446744073709551615 Oct 16 '17 at 13:42
  • Is there anyway to improve the accessibility of this solution by adding alt text to the image? Currently the TalkBack screen reader in android reads the [img src=.../] text verbatim. It would be nice to be able to set [img src=.../ altText="Image Description"] and have the screen reader read the alt text. – justarandomuser12345 Feb 01 '18 at 23:03
  • Thanks for this awesome answer. I have converted this to Kotlin. https://gist.github.com/WSAyan/29813414e45aae16bf351dd501fb3270 – WS Ayan Aug 09 '20 at 10:51
27

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.

Pavel Kataykin
  • 1,527
  • 15
  • 14
21
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

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

Linh
  • 57,942
  • 23
  • 262
  • 279
12

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>
Community
  • 1
  • 1
Reinstate Monica
  • 2,767
  • 3
  • 31
  • 40
  • 3
    This is a good solution. I would suggest only an improvement: add drawable.mutate() before drawable.setColorFilter; if not doing so, you will have the drawable with a different color in other parts of your app. – moondroid Mar 27 '17 at 08:26
  • @moondroid Thank you for the suggestion, I edited the answer accordingly. – Reinstate Monica Mar 27 '17 at 12:11
  • Actually i had a problem, because my drawable is not square, and this solution will always make the drawable width same with the drawable height, it will resize my drawable unproportionally – HendraWD Aug 31 '17 at 10:47
1

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

kip2
  • 6,473
  • 4
  • 55
  • 72
1

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"
Partha Paul
  • 189
  • 4
0

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);
0

Did a component specifically for adding inline an Image inline to a text. it supports any given position and handles click on Image: https://github.com/myDario/DarioInlineImageTextView

Raphael C
  • 2,296
  • 1
  • 22
  • 22