É possível exibir imagens inline de html em um Android TextView?

87

Dado o seguinte HTML:

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

É possível fazer a renderização da imagem? Ao usar este snippet:, mContentText.setText(Html.fromHtml(text));recebo uma caixa ciano com bordas pretas, o que me leva a acreditar que um TextView tem alguma ideia do que é uma tag img.

Gunnar Lium
fonte
1
Confira minha postagem @ javatechig.com javatechig.com/2013/04/07/how-to-display-html-in-android-view
Nilanchal

Respostas:

125

Se você der uma olhada na documentação,Html.fromHtml(text) verá que diz:

Todas as <img>tags no HTML serão exibidas como uma imagem de substituição genérica que seu programa pode percorrer e substituir por imagens reais.

Se você não quiser fazer essa substituição sozinho, pode usar o outro Html.fromHtml()método que recebe os argumentos an Html.TagHandlere an Html.ImageGetter, bem como o texto para analisar.

No seu caso, você poderia analisar nullcomo para o, Html.TagHandlermas precisaria implementar o seu próprio, Html.ImageGetterpois não há uma implementação padrão.

No entanto, o problema que você terá é que o Html.ImageGetterprecisa ser executado de forma síncrona e, se você estiver baixando imagens da web, provavelmente desejará fazer isso de forma assíncrona. Se você pode adicionar qualquer imagem que deseja exibir como recursos em seu aplicativo, sua ImageGetterimplementação se torna muito mais simples. Você poderia se safar com algo como:

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;
    }
};

Você provavelmente gostaria de descobrir algo mais inteligente para mapear strings de origem para IDs de recursos.

Dave Webb
fonte
4
ESTÁ BEM. Achei mais fácil usar apenas um WebView. No entanto, posso ver sua técnica sendo útil para outros cenários semelhantes. Obrigado!
Gunnar Lium
1
a maneira mais inteligente de obter a id do recurso de name é usar Resources.getIdentifier (String name, String defType, String defPackage).
Timuçin
@Gunnar Lium ... mas i8mage não está aparecendo no webview .. !! Alguma ajuda ??
kgandroid
Se a imagem está em um servidor, como podemos obter a imagem ... no meu caso a imagem é dinâmica ... Não posso usar outras visualizações de imagem porque não é certo que deve haver uma imagem ...
Sourav Roy
19

Eu implementei em meu aplicativo, peguei muito a referência do pskink .thanx

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;
    }
}
madhu527
fonte
1
Hey, what exactly is the purpose of "addlevel" and "setLevel" ?
Aeefire
Is there a way of centralizing the images? It would also be good if we could tap them and have them shown in whatever image viewer app we have installed.
Aspiring Dev
I think you forgot the context for my question. I know how to open an image file with an image viewer app but your answer puts bitmaps inside a TextView and as far as I could tell there is no way of discerning when a user is tapping a specific image inside of it. This is more of an issue if you have many images inside the textview. Is there a way to do this?
Aspiring Dev
but one issue is that it scrolls very slow, can we do something about that?
Pratik Jamariya
try to add smooth scrolls listeners
madhu527
16

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);
drawk
fonte
This combination would be perfect with AsyncTask retrieving of internet.
Francis Rodrigues
1
Thanks! It solved my problem. I only need local images, so just drop them to drawable folder and make sure to remove the image extention when calling it from html.
Dody Rachmat Wicaksono
Thanks! But beware source may be null, and getIdentifier() crashes in this case. Better add an explicit check.
gmk57
5

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

Julian
fonte
3

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);
Blacklight
fonte
1

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.

Falmarri
fonte
1

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]

splash
fonte
0

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

logcat
fonte
0

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!!!!!

Farmaker
fonte