Est-il possible d'afficher des images en ligne à partir de HTML dans un Android TextView?

87

Compte tenu du code HTML suivant:

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

Est-il possible de rendre l'image? Lorsque mContentText.setText(Html.fromHtml(text));j'utilise cet extrait :, j'obtiens une boîte cyan avec des bordures noires, ce qui me porte à croire qu'un TextView a une idée de ce qu'est une balise img.

Gunnar Lium
la source
1
Consulter mon message @ javatechig.com javatechig.com/2013/04/07/how-to-display-html-in-android-view
Nilanchal

Réponses:

125

Si vous regardez la documentation,Html.fromHtml(text) vous verrez qu'elle dit:

Toutes les <img>balises dans le HTML s'afficheront comme une image de remplacement générique que votre programme peut ensuite parcourir et remplacer par des images réelles.

Si vous ne voulez pas faire ce remplacement vous-même, vous pouvez utiliser l'autre Html.fromHtml()méthode qui prend un Html.TagHandleret un Html.ImageGettercomme arguments ainsi que le texte à analyser.

Dans votre cas, vous pouvez analyser nullcomme pour le Html.TagHandlermais vous devez implémenter le vôtre Html.ImageGettercar il n'y a pas d'implémentation par défaut.

Cependant, le problème que vous allez avoir est que Html.ImageGettervous devez exécuter de manière synchrone et si vous téléchargez des images à partir du Web, vous voudrez probablement le faire de manière asynchrone. Si vous pouvez ajouter des images que vous souhaitez afficher en tant que ressources dans votre application, votre ImageGetterimplémentation devient beaucoup plus simple. Vous pourriez vous en tirer avec quelque chose comme:

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

Vous voudrez probablement trouver quelque chose de plus intelligent pour mapper les chaînes source aux ID de ressources.

Dave Webb
la source
4
D'ACCORD. J'ai trouvé qu'il était plus facile d'utiliser simplement une WebView à la place. Je peux cependant voir que votre technique est utile pour d'autres scénarios similaires. Merci!
Gunnar Lium
1
la manière la plus intelligente d'obtenir l'ID de ressource à partir du nom est d'utiliser Resources.getIdentifier (String name, String defType, String defPackage).
Timuçin
@Gunnar Lium ... mais i8mage n'apparaît pas dans la vue Web .. !! Toute aide ??
kgandroid
Si l'image est dans un serveur alors comment on peut obtenir l'image… dans mon cas l'image est dynamique… Je ne peux pas utiliser d'autres vues d'image car il n'est pas certain qu'il doit y avoir une image…
Sourav Roy
19

J'ai implémenté dans mon application, pris beaucoup de références du 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);
        }
    }
}
}

Comme ci-dessous, le commentaire @rpgmaker, j'ai ajouté cette réponse

oui, vous pouvez le faire en utilisant la classe ResolveInfo

vérifiez que votre fichier est pris en charge avec les applications déjà installées ou non

en utilisant le code ci-dessous:

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
la source
1
Hé, quel est exactement le but de "addlevel" et "setLevel"?
Aeefire
Existe-t-il un moyen de centraliser les images? Ce serait également bien si nous pouvions les toucher et les afficher dans n'importe quelle application de visualisation d'images que nous avons installée.
Aspiring Dev
Je pense que vous avez oublié le contexte de ma question. Je sais comment ouvrir un fichier image avec une application de visionneuse d'images, mais votre réponse place des bitmaps dans un TextView et, pour autant que je sache, il n'y a aucun moyen de discerner quand un utilisateur appuie sur une image spécifique à l'intérieur. C'est plus un problème si vous avez de nombreuses images dans la vue de texte. Y a-t-il un moyen de faire cela?
Aspiring Dev
mais un problème est qu'il défile très lentement, pouvons-nous faire quelque chose à ce sujet?
Pratik Jamariya
essayez d'ajouter des auditeurs de défilement lisses
madhu527
16

C'est ce que j'utilise, qui n'a pas besoin que vous durcissiez les noms de vos ressources et cherchera d'abord les ressources dessinables dans les ressources de vos applications, puis dans les ressources Android en stock si rien n'a été trouvé - vous permettant d'utiliser des icônes par défaut et autres.

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

 }

Qui peut être utilisé tel quel (exemple):

String myHtml = "This will display an image to the right <img src='ic_menu_more' />";
myTextview.setText(Html.fromHtml(myHtml, new ImageGetter(), null);
dessiner
la source
Cette combinaison serait parfaite avec la récupération AsyncTask d'Internet.
Francis Rodrigues
1
Merci! Cela a résolu mon problème. Je n'ai besoin que d'images locales, alors déposez-les simplement dans un dossier pouvant être dessiné et assurez-vous de supprimer l'extension d'image lorsque vous l'appelez depuis html.
Dody Rachmat Wicaksono
Merci! Mais attention sourcepeut être nul, et se getIdentifier()bloque dans ce cas. Mieux vaut ajouter une vérification explicite.
gmk57
5

J'ai rencontré le même problème et j'ai trouvé une solution assez propre: après Html.fromHtml (), vous pouvez exécuter une AsyncTask qui itère sur toutes les balises, récupère les images puis les affiche.

Ici vous pouvez trouver du code que vous pouvez utiliser (mais il nécessite une certaine personnalisation): https://gist.github.com/1190397

julien
la source
3

J'ai utilisé la réponse de Dave Webb mais je l'ai un peu simplifiée. Tant que les ID de ressources resteront les mêmes pendant l'exécution dans votre cas d'utilisation, il n'est pas vraiment nécessaire d'écrire votre propre implémentation de classe Html.ImageGetteret de jouer avec les chaînes source.

Ce que j'ai fait, c'est d'utiliser l'ID de ressource comme chaîne source:

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

et utilisez-le directement:

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);
Lumière noire
la source
1

Vous pouvez également écrire votre propre analyseur pour extraire l'URL de toutes les images, puis créer dynamiquement de nouvelles vues d'image et transmettre les URL.

Falmarri
la source
1

De plus, si vous souhaitez effectuer le remplacement vous-même, le caractère que vous devez rechercher est [].

Mais si vous utilisez Eclipse, il paniquera lorsque vous tapez cette lettre dans une instruction [replace] vous indiquant qu'elle est en conflit avec Cp1252 - c'est un bogue Eclipse. Pour résoudre ce problème, accédez à

Fenêtre -> Préférences -> Général -> Espace de travail -> Encodage de fichier texte,

et sélectionnez [UTF-8]

éclaboussure
la source
0

Au cas où quelqu'un pense que les ressources doivent être déclaratives et que l'utilisation de Spannable pour plusieurs langues est un gâchis, j'ai fait une vue personnalisée

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

suivre les mises à jour, le cas échéant, sur https://gist.github.com/logcat/64234419a935f1effc67

logcat
la source
0

KOTLIN

Il y a aussi la possibilité d'utiliser sufficientlysecure.htmltextview.HtmlTextView

Utilisez comme ci-dessous dans les fichiers gradle:

Fichier de classement du projet:

repositories {
    jcenter()
}

Fichier de note d'application:

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

Dans le fichier xml, remplacez votre textView par:

<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}" />

La dernière ligne ci-dessus est si vous utilisez des adaptateurs de liaison où le code sera comme:

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

Plus d'infos sur la page github et un grand merci aux auteurs !!!!!

Agriculteur
la source