¿Es posible mostrar imágenes en línea desde html en un TextView de Android?

87

Dado el siguiente HTML:

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

¿Es posible renderizar la imagen? Cuando uso este fragmento:, mContentText.setText(Html.fromHtml(text));obtengo un cuadro cian con bordes negros, lo que me lleva a creer que un TextView tiene alguna idea de lo que es una etiqueta img.

Gunnar Lium
fuente
1
Revisa mi publicación @ javatechig.com javatechig.com/2013/04/07/how-to-display-html-in-android-view
Nilanchal

Respuestas:

125

Si echas un vistazo a la documentaciónHtml.fromHtml(text) , verás que dice:

Cualquier <img>etiqueta en el HTML se mostrará como una imagen de reemplazo genérica que su programa puede revisar y reemplazar con imágenes reales.

Si no desea hacer este reemplazo usted mismo, puede usar el otro Html.fromHtml()método que toma un Html.TagHandlery un Html.ImageGettercomo argumentos, así como el texto para analizar.

En su caso, podría analizar nullcomo para el Html.TagHandlerpero necesitaría implementar su propioHtml.ImageGetter ya que no hay una implementación predeterminada.

Sin embargo, el problema que tendrá es que Html.ImageGetterdebe ejecutarse de forma sincrónica y, si está descargando imágenes de la web, probablemente querrá hacerlo de forma asincrónica. Si puede agregar las imágenes que desea mostrar como recursos en su aplicación, su ImageGetterimplementación se vuelve mucho más simple. Podrías salirte con la tuya con 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;
    }
};

Sin embargo, probablemente querrá encontrar algo más inteligente para mapear cadenas de origen a ID de recursos.

Dave Webb
fuente
4
OKAY. Encontré que era más fácil usar WebView en su lugar. Sin embargo, puedo ver que su técnica es útil para otros escenarios similares. ¡Gracias!
Gunnar Lium
1
la forma más inteligente de obtener la identificación del recurso del nombre es usar Resources.getIdentifier (String name, String defType, String defPackage).
Timuçin
@Gunnar Lium ... pero i8mage no se muestra en la vista web .. !! ¿Alguna ayuda?
kgandroid
Si la imagen está en un servidor, entonces, ¿cómo podemos obtener la imagen? ... en mi caso, la imagen es dinámica ... No puedo usar otras vistas de imagen porque no es seguro que tenga que haber una imagen ...
Sourav Roy
19

Lo he implementado en mi aplicación, tomado referencia del pskink .thanx mucho

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

Según el comentario de @rpgmaker a continuación, agregué esta respuesta

sí, puedes hacerlo usando la clase ResolveInfo

compruebe que su archivo sea compatible con aplicaciones ya instaladas o no

usando el siguiente código:

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
fuente
1
Oye, ¿cuál es exactamente el propósito de "addlevel" y "setLevel"?
Aeefire
¿Existe alguna forma de centralizar las imágenes? También sería bueno si pudiéramos tocarlos y mostrarlos en cualquier aplicación de visor de imágenes que hayamos instalado.
Aspiring Dev
Creo que olvidaste el contexto de mi pregunta. Sé cómo abrir un archivo de imagen con una aplicación de visor de imágenes, pero su respuesta coloca mapas de bits dentro de un TextView y, por lo que pude ver, no hay forma de discernir cuándo un usuario está tocando una imagen específica dentro de él. Esto es un problema mayor si tiene muchas imágenes dentro de la vista de texto. ¿Hay alguna forma de hacer esto?
Desarrollador aspirante
pero un problema es que se desplaza muy lento, ¿podemos hacer algo al respecto?
Pratik Jamariya
tratar de agregar oyentes de pergaminos suaves
madhu527
16

Esto es lo que yo uso, que no necesita que le pongas énfasis a los nombres de tus recursos y buscará los recursos dibujables primero en los recursos de tus aplicaciones y luego en los recursos de Android si no se encuentra nada, lo que te permite usar íconos predeterminados y demás.

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

 }

Que se puede utilizar como tal (ejemplo):

String myHtml = "This will display an image to the right <img src='ic_menu_more' />";
myTextview.setText(Html.fromHtml(myHtml, new ImageGetter(), null);
dibujar
fuente
Esta combinación sería perfecta con la recuperación de AsyncTask de Internet.
Francis Rodrigues
1
¡Gracias! Resolvió mi problema. Solo necesito imágenes locales, así que colóquelas en la carpeta dibujable y asegúrese de eliminar la extensión de la imagen cuando la llame desde html.
Dody Rachmat Wicaksono
¡Gracias! Pero cuidado, sourcepuede ser nulo, y getIdentifier()en este caso se bloquea. Mejor agregue un cheque explícito.
gmk57
5

Me enfrenté al mismo problema y encontré una solución bastante limpia: después de Html.fromHtml () puede ejecutar una AsyncTask que itera sobre todas las etiquetas, recupera las imágenes y luego las muestra.

Aquí puede encontrar algún código que puede usar (pero necesita algo de personalización): https://gist.github.com/1190397

Julian
fuente
3

Usé la respuesta de Dave Webb pero la simplifiqué un poco. Siempre que los ID de recursos permanezcan iguales durante el tiempo de ejecución en su caso de uso, no es realmente necesario escribir su propia clase implementandoHtml.ImageGetter y jugar con cadenas de origen.

Lo que hice fue usar el ID de recurso como una cadena de origen:

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

y utilícelo directamente:

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);
Luz negra
fuente
1

También puede escribir su propio analizador para extraer la URL de todas las imágenes y luego crear dinámicamente nuevas vistas de imágenes y pasar las URL.

Falmarri
fuente
1

Además, si desea realizar el reemplazo usted mismo, el personaje que debe buscar es [].

Pero si estás usando Eclipse, se asustará cuando escribas esa letra en una declaración [reemplazar] diciéndote que entra en conflicto con Cp1252 - este es un error de Eclipse. Para solucionarlo, ve a

Ventana -> Preferencias -> General -> Espacio de trabajo -> Codificación de archivos de texto,

y seleccione [UTF-8]

chapoteo
fuente
0

En caso de que alguien piense que los recursos deben ser declarativos y usar Spannable para múltiples idiomas es un desastre, hice una vista personalizada

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

realizar un seguimiento de las actualizaciones, si las hay, en https://gist.github.com/logcat/64234419a935f1effc67

logcat
fuente
0

KOTLIN

También existe la posibilidad de utilizar sufficientlysecure.htmltextview.HtmlTextView

Use como se muestra a continuación en los archivos gradle:

Archivo de proyecto gradle:

repositories {
    jcenter()
}

Archivo gradle de la aplicación:

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

Dentro del archivo xml reemplace su textView con:

<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 última línea de arriba es si usa adaptadores de enlace donde el código será como:

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

Más información de la página de github y muchas gracias a los autores !!!!!

Farmaker
fuente