Is it possible to display inline images from html in an Android TextView?

AndroidImageTextview

Android Problem Overview


Given the following HTML:

<p>This is text and this is an image <img src="http://www.example.com/image.jpg" />.</p>

Is it possible to make the image render? When using this snippet: mContentText.setText(Html.fromHtml(text));, I get a cyan box with black borders, leading me to believe that a TextView has some idea of what an img tag is.

Android Solutions


Solution 1 - Android

If you have a look at the documentation for Html.fromHtml(text) you'll see it says:

> Any <img> tags in the HTML will display as a generic replacement image which your program can then go through and replace with real images.

If you don't want to do this replacement yourself you can use the other Html.fromHtml() method which takes an Html.TagHandler and an Html.ImageGetter as arguments as well as the text to parse.

In your case you could parse null as for the Html.TagHandler but you'd need to implement your own Html.ImageGetter as there isn't a default implementation.

However, the problem you're going to have is that the Html.ImageGetter needs to run synchronously and if you're downloading images from the web you'll probably want to do that asynchronously. If you can add any images you want to display as resources in your application the your ImageGetter implementation becomes a lot simpler. You could get away with something like:

private class ImageGetter implements Html.ImageGetter {
	
	public Drawable getDrawable(String source) {
		int id;
		
		if (source.equals("stack.jpg")) {
			id = R.drawable.stack;
		}
		else if (source.equals("overflow.jpg")) {
			id = R.drawable.overflow;
		}
		else {
			return null;
		}

		Drawable d = getResources().getDrawable(id);
		d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
		return d;
	}
};

You'd probably want to figure out something smarter for mapping source strings to resource IDs though.

Solution 2 - Android

I have implemented in my app ,taken referece from the pskink.thanx a lot

package com.example.htmltagimg;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LevelListDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.text.Spanned;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends Activity implements ImageGetter {
private final static String TAG = "TestImageGetter";
private TextView mTv;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    String source = "this is a test of <b>ImageGetter</b> it contains " +
            "two images: <br/>" +
            "<img src=\"http://developer.android.com/assets/images/dac_logo.png\"><br/>and<br/>" +
            "<img src=\"http://www.hdwallpapersimages.com/wp-content/uploads/2014/01/Winter-Tiger-Wild-Cat-Images.jpg\">";
    String imgs="<p><img alt=\"\" src=\"http://images.visitcanberra.com.au/images/canberra_hero_image.jpg\" style=\"height:50px; width:100px\" />Test Article, Test Article, Test Article, Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,v</p>";
    String src="<p><img alt=\"\" src=\"http://stylonica.com/wp-content/uploads/2014/02/Beauty-of-nature-random-4884759-1280-800.jpg\" />Test Attractions Test Attractions Test Attractions Test Attractions</p>";
    String img="<p><img alt=\"\" src=\"/site_media/photos/gallery/75b3fb14-3be6-4d14-88fd-1b9d979e716f.jpg\" style=\"height:508px; width:640px\" />Test Article, Test Article, Test Article, Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,Test Article,v</p>";
    Spanned spanned = Html.fromHtml(imgs, this, null);
    mTv = (TextView) findViewById(R.id.text);
    mTv.setText(spanned);
}

@Override
public Drawable getDrawable(String source) {
    LevelListDrawable d = new LevelListDrawable();
    Drawable empty = getResources().getDrawable(R.drawable.ic_launcher);
    d.addLevel(0, 0, empty);
    d.setBounds(0, 0, empty.getIntrinsicWidth(), empty.getIntrinsicHeight());

    new LoadImage().execute(source, d);

    return d;
}

class LoadImage extends AsyncTask<Object, Void, Bitmap> {

    private LevelListDrawable mDrawable;

    @Override
    protected Bitmap doInBackground(Object... params) {
        String source = (String) params[0];
        mDrawable = (LevelListDrawable) params[1];
        Log.d(TAG, "doInBackground " + source);
        try {
            InputStream is = new URL(source).openStream();
            return BitmapFactory.decodeStream(is);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        Log.d(TAG, "onPostExecute drawable " + mDrawable);
        Log.d(TAG, "onPostExecute bitmap " + bitmap);
        if (bitmap != null) {
            BitmapDrawable d = new BitmapDrawable(bitmap);
            mDrawable.addLevel(1, 1, d);
            mDrawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
            mDrawable.setLevel(1);
            // i don't know yet a better way to refresh TextView
            // mTv.invalidate() doesn't work as expected
            CharSequence t = mTv.getText();
            mTv.setText(t);
        }
    }
}
}

As per below @rpgmaker comment i added this answer

yes you can do using ResolveInfo class

check your file is supported with already installed apps or not

using below code:

private boolean isSupportedFile(File file) throws PackageManager.NameNotFoundException {
    PackageManager pm = mContext.getPackageManager();
    java.io.File mFile = new java.io.File(file.getFileName());
    Uri data = Uri.fromFile(mFile);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(data, file.getMimeType());
    List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);

    if (resolveInfos != null && resolveInfos.size() > 0) {
        Drawable icon = mContext.getPackageManager().getApplicationIcon(resolveInfos.get(0).activityInfo.packageName);
        Glide.with(mContext).load("").placeholder(icon).into(binding.fileAvatar);
        return true;
    } else {
        Glide.with(mContext).load("").placeholder(R.drawable.avatar_defaultworkspace).into(binding.fileAvatar);
        return false;
    }
}

Solution 3 - Android

This is what I use, which does not need you to hardcore your resource names and will look for the drawable resources first in your apps resources and then in the stock android resources if nothing was found - allowing you to use default icons and such.

private class ImageGetter implements Html.ImageGetter {

     public Drawable getDrawable(String source) {
        int id;
         
        id = getResources().getIdentifier(source, "drawable", getPackageName());
        
        if (id == 0) {
        	// the drawable resource wasn't found in our package, maybe it is a stock android drawable?
        	id = getResources().getIdentifier(source, "drawable", "android");
        }
        
        if (id == 0) {
        	// prevent a crash if the resource still can't be found
        	return null;	
        }
        else {
        	Drawable d = getResources().getDrawable(id);
        	d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
        	return d;
        }
     }
     
 }

Which can be used as such (example):

String myHtml = "This will display an image to the right <img src='ic_menu_more' />";
myTextview.setText(Html.fromHtml(myHtml, new ImageGetter(), null);

Solution 4 - Android

I faced the same problem and I've found a pretty clean solution: After Html.fromHtml() you can run an AsyncTask that iterates over all the tags, fetches the images and then displays them.

Here you can find some code that you can use (but it needs some customization): https://gist.github.com/1190397

Solution 5 - Android

I used Dave Webb's answer but simplified it a bit. As long as the resource IDs will stay the same during runtime in your use-case, there's not really a need to write your own class implementing Html.ImageGetter and mess around with source-strings.

What I did was using the resource ID as a source-string:

final String img = String.format("<img src=\"%s\"/>", R.drawable.your_image);
final String html = String.format("Image: %s", img);

and use it directly:

Html.fromHtml(html, new Html.ImageGetter() {
  @Override
  public Drawable getDrawable(final String source) {
    Drawable d = null;
    try {
      d = getResources().getDrawable(Integer.parseInt(source));
      d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
    } catch (Resources.NotFoundException e) {
      Log.e("log_tag", "Image not found. Check the ID.", e);
    } catch (NumberFormatException e) {
      Log.e("log_tag", "Source string not a valid resource ID.", e);
    }

    return d;
  }
}, null);

Solution 6 - Android

KOTLIN

There is also the possibility to use sufficientlysecure.htmltextview.HtmlTextView

Use like below in gradle files:

Project gradle file:

repositories {
    jcenter()
}

App gradle file:

dependencies {
implementation 'org.sufficientlysecure:html-textview:3.9'
}

Inside xml file replace your textView with:

<org.sufficientlysecure.htmltextview.HtmlTextView
      android:id="@+id/allNewsBlockTextView"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="2dp"
      android:textColor="#000"
      android:textSize="18sp"
      app:htmlToString="@{detailsViewModel.selectedText}" />

Last line above is if you use Binding adapters where the code will be like:

@BindingAdapter("htmlToString")
fun bindTextViewHtml(textView: HtmlTextView, htmlValue: String) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    textView.setHtml(
        htmlValue,
        HtmlHttpImageGetter(textView, "n", true)
    );
    } else {
        textView.setHtml(
        htmlValue,
        HtmlHttpImageGetter(textView, "n", true)
        );
    }
}

More info from github page and a big thank you to the authors!!!!!

Solution 7 - Android

Also, if you do want to do the replacement yourself, the character you need to look for is [  ].

But if you're using Eclipse, it will freak out when you type that letter into a [replace] statement telling you it conflicts with Cp1252 - this is an Eclipse bug. To fix it, go to

> Window -> Preferences -> General -> Workspace -> Text file encoding,

and select [UTF-8]

Solution 8 - Android

You could also write your own parser to pull the URL of all the images and then dynamically create new imageviews and pass in the urls.

Solution 9 - Android

In case somebody think that resources must be declarative and using Spannable for multiple languages is a mess, I did some custom view

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.text.Spanned;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * XXX does not support android:drawable, only current app packaged icons
 *
 * Use it with strings like <string name="text"><![CDATA[Some text <img src="some_image"></img> with image in between]]></string>
 * assuming there is @drawable/some_image in project files
 *
 * Must be accompanied by styleable
 * <declare-styleable name="HtmlTextView">
 *    <attr name="android:text" />
 * </declare-styleable>
 */

public class HtmlTextView extends TextView {

    public HtmlTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HtmlTextView);
        String html = context.getResources().getString(typedArray.getResourceId(R.styleable.HtmlTextView_android_text, 0));
        typedArray.recycle();

        Spanned spannedFromHtml = Html.fromHtml(html, new DrawableImageGetter(), null);
        setText(spannedFromHtml);
    }

    private class DrawableImageGetter implements ImageGetter {
        @Override
        public Drawable getDrawable(String source) {
            Resources res = getResources();
            int drawableId = res.getIdentifier(source, "drawable", getContext().getPackageName());
            Drawable drawable = res.getDrawable(drawableId, getContext().getTheme());

            int size = (int) getTextSize();
            int width = size;
            int height = size;

//            int width = drawable.getIntrinsicWidth();
//            int height = drawable.getIntrinsicHeight();

            drawable.setBounds(0, 0, width, height);
            return drawable;
        }
    }
}

track updates, if any, at https://gist.github.com/logcat/64234419a935f1effc67

Solution 10 - Android

I used below code and worked for me to load HTMl and <img> tag inside Android TextView using Html.ImageGetter and AsyncTask:

String text = your html code;

Spanned spanned = Html.fromHtml(text,
        new Html.ImageGetter() {
            @Override
            public Drawable getDrawable(String source) {
                LevelListDrawable d = new LevelListDrawable();
                Drawable empty = getResources().getDrawable(R.drawable.abc_btn_check_material);
                ;
                d.addLevel(0, 0, empty);
                d.setBounds(0, 0, empty.getIntrinsicWidth(), empty.getIntrinsicHeight());
                new ImageGetterAsyncTask(yourActivity.this, source, d).execute(binding.yourTextView);

                return d;
            }
        }, new MyHtmlTagHandler());
                        binding.yourTextView.setText(spanned);

Then you should add this AsyncTask class to your code to load images:

class ImageGetterAsyncTask extends AsyncTask<TextView, Void, Bitmap> {

        private LevelListDrawable levelListDrawable;
        private Context context;
        private String source;
        private TextView t;

        public ImageGetterAsyncTask(Context context, String source, LevelListDrawable levelListDrawable) {
            this.context = context;
            this.source = source;
            this.levelListDrawable = levelListDrawable;
        }

        @Override
        protected Bitmap doInBackground(TextView... params) {
            t = params[0];
            try {
                Log.d(TAG, "Downloading the image from: " + source);

                return Picasso.with(context).load(source).get();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

        @Override
        protected void onPostExecute(final Bitmap bitmap) {
            try {
                Drawable d = new BitmapDrawable(context.getResources(), bitmap);
                Point size = new Point();
                ((Activity) context).getWindowManager().getDefaultDisplay().getSize(size);
                // Lets calculate the ratio according to the screen width in px
                int multiplier = size.x / bitmap.getWidth();
                Log.d(TAG, "multiplier: " + multiplier);
                levelListDrawable.addLevel(1, 1, d);
                // Set bounds width  and height according to the bitmap resized size
                levelListDrawable.setBounds(0, 0, bitmap.getWidth() * multiplier, bitmap.getHeight() * multiplier);
                levelListDrawable.setLevel(1);
                t.setText(t.getText()); // invalidate() doesn't work correctly...
            } catch (Exception e) { /* Like a null bitmap, etc. */ }
        }
    }

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
QuestionGunnar LiumView Question on Stackoverflow
Solution 1 - AndroidDave WebbView Answer on Stackoverflow
Solution 2 - Androidmadhu527View Answer on Stackoverflow
Solution 3 - AndroiddrawkView Answer on Stackoverflow
Solution 4 - AndroidJulianView Answer on Stackoverflow
Solution 5 - AndroidBlacklightView Answer on Stackoverflow
Solution 6 - AndroidFarmakerView Answer on Stackoverflow
Solution 7 - AndroidsplashView Answer on Stackoverflow
Solution 8 - AndroidFalmarriView Answer on Stackoverflow
Solution 9 - AndroidlogcatView Answer on Stackoverflow
Solution 10 - AndroidGhazalView Answer on Stackoverflow