How to add image in a TextView text?
Asked Answered
T

9

98

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.

Threewheeler answered 12/3, 2013 at 2:55 Comment(0)
C
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"/>
Caryopsis answered 12/3, 2013 at 3:19 Comment(7)
I got an error "Cannot make a static reference to the non-static method setCompoundDrawablesWithIntrinsicBounds(int, int, int, int) from the type TextView"Threewheeler
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.Threewheeler
@Umesh Lakhani: How is it possible to put multiple drawables in text by this approach?Childs
In XML an image is drawn from left, not in center.Libra
HI @Umesh. How to set some margin to it. setCompoundDrawablePadding isn't doing anythingIcao
@Prabs, did it get fixed? I want to add image instead of text in edittext..drawableLeft or right is not appropriate in my case..Whitley
@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..Whitley
B
80

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

Barthol answered 21/1, 2014 at 6:27 Comment(19)
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.Barrault
@AnasAzeem are you able to show ok16 and retry16 "normally", via ImageView? Did you specify TextViewWithImages in place of TextView?Barthol
don't forget to change <com.xyz.customandroid.TextViewWithImages with <YourPackageName.TextViewWithImages otherwise you got an error in inflating and NoClassFound exceptionAnoint
@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.Barthol
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.Presbytery
its working but i cant set height width of image and also not getting image in center of textTrimmer
@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.Barthol
@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.Portugal
If we bitmap then how we can deal with this ?Glary
@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<String, Bitmap>. 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<Bitmap> rather than Bitmap.Barthol
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.Bangtail
This class works perfectly fine until we create a release apk, if anyone found a solution for that then please help with the solution.Innervate
@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.Cite
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.Innervate
What does the forloop in the addImages() method do? Can't figure out the purpose of it.Correna
@Correna In theory, there may already be one or more image spans. The loop removes them.Barthol
@Correna 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.Barthol
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.Hinny
Thanks for this awesome answer. I have converted this to Kotlin. gist.github.com/WSAyan/29813414e45aae16bf351dd501fb3270Coauthor
R
28

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.

Ries answered 28/6, 2017 at 10:19 Comment(2)
in that case image added but align with baseline of text i want to align with top of text can you help meNeedlefish
it works but how can we resize the image icon according to text sizeOrth
C
23
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://mcmap.net/q/218582/-align-text-around-imagespan-center-vertical

Using

val textView = findViewById<TextView>(R.id.textview)
textView.setText("Send an [email-icon] to [email protected].")
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

Chef answered 14/1, 2021 at 7:2 Comment(0)
P
11

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>
Portugal answered 16/8, 2016 at 14:19 Comment(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.Malcolmmalcom
@Malcolmmalcom Thank you for the suggestion, I edited the answer accordingly.Portugal
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 unproportionallyLecroy
R
2

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"
Rolfston answered 15/9, 2021 at 11:30 Comment(0)
B
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

Belisle answered 12/10, 2017 at 13:25 Comment(0)
R
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);
Rufe answered 25/10, 2020 at 9:46 Comment(0)
M
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

Mcadoo answered 3/7, 2022 at 9:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.