Czy można wyświetlać obrazy wbudowane z html w Android TextView?

87

Biorąc pod uwagę następujący kod HTML:

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

Czy można renderować obraz? Korzystając z tego fragmentu kodu:, mContentText.setText(Html.fromHtml(text));otrzymuję niebieskie pole z czarnymi obramowaniami, co prowadzi mnie do przekonania, że ​​TextView ma pewne pojęcie o tym, czym jest tag img.

Gunnar Lium
źródło
1
Sprawdź mój post @ javatechig.com javatechig.com/2013/04/07/how-to-display-html-in-android-view
Nilanchal

Odpowiedzi:

125

Jeśli spojrzysz na dokumentacjęHtml.fromHtml(text) , zobaczysz, że mówi:

Wszelkie <img>tagi w kodzie HTML będą wyświetlane jako ogólny obraz zastępczy, przez który Twój program może następnie przejść i zastąpić rzeczywistymi obrazami.

Jeśli nie chcesz wykonywać tej zamiany samodzielnie, możesz użyć innej Html.fromHtml()metody, która przyjmuje argumenty an Html.TagHandleri an Html.ImageGetterjako argumenty, a także tekst do przeanalizowania.

W twoim przypadku możesz przeanalizować nulljak w przypadku, Html.TagHandlerale musisz zaimplementować własną, Html.ImageGetterponieważ nie ma domyślnej implementacji.

Jednak problem, który będziesz mieć, polega na tym, że Html.ImageGettermusi działać synchronicznie, a jeśli pobierasz obrazy z Internetu, prawdopodobnie będziesz chciał to zrobić asynchronicznie. Jeśli możesz dodać obrazy, które chcesz wyświetlić jako zasoby w aplikacji, ImageGetterimplementacja stanie się znacznie prostsza. Możesz uciec z czymś takim:

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

Prawdopodobnie jednak chciałbyś wymyślić coś mądrzejszego do mapowania ciągów źródłowych na identyfikatory zasobów.

Dave Webb
źródło
4
OK. Zauważyłem, że zamiast tego łatwiej jest po prostu użyć WebView. Widzę jednak, że twoja technika jest przydatna w innych podobnych scenariuszach. Dzięki!
Gunnar Lium
1
Sprytniejszym sposobem uzyskania identyfikatora zasobu z name jest użycie Resources.getIdentifier (String name, String defType, String defPackage).
Timuçin
@Gunnar Lium ... ale i8mage nie wyświetla się w widoku internetowym .. !! Jakaś pomoc?
kgandroid
Jeśli obraz jest na serwerze, to w jaki sposób możemy go uzyskać… w moim przypadku obraz jest dynamiczny… Nie mogę używać innych widoków obrazu, ponieważ nie jest pewne, czy musi istnieć obraz…
Sourav Roy
19

Zaimplementowałem w swojej aplikacji, bardzo korzystałem z 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);
        }
    }
}
}

Zgodnie z komentarzem @rpgmaker dodałem tę odpowiedź

tak, możesz to zrobić używając klasy ResolveInfo

sprawdź, czy Twój plik jest obsługiwany przez już zainstalowane aplikacje, czy nie

używając poniższego kodu:

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
źródło
1
Hej, jaki dokładnie jest cel „addlevel” i „setLevel”?
Aeefire
Czy istnieje sposób na centralizację obrazów? Byłoby również dobrze, gdybyśmy mogli je dotknąć i wyświetlić w dowolnej zainstalowanej aplikacji do przeglądania obrazów.
Aspiring Dev
Myślę, że zapomniałeś kontekstu mojego pytania. Wiem, jak otworzyć plik obrazu za pomocą aplikacji do przeglądania obrazów, ale Twoja odpowiedź umieszcza mapy bitowe w TextView i, o ile wiem, nie ma sposobu, aby rozpoznać, kiedy użytkownik dotyka określonego obrazu w nim. Jest to bardziej problem, jeśli masz wiele obrazów w widoku tekstu. Czy jest na to sposób?
Aspiring Dev
ale jedną kwestią jest to, że przewija się bardzo wolno, czy możemy coś z tym zrobić?
Pratik Jamariya
spróbuj dodać płynne przewijanie słuchaczy
madhu527
16

To jest to, czego używam, co nie wymaga od ciebie hardcore'owania nazw zasobów i będzie szukać zasobów do rysowania najpierw w zasobach aplikacji, a następnie w zasobach Androida, jeśli nic nie zostało znalezione - co pozwala na użycie domyślnych ikon i tym podobnych.

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

 }

Które można wykorzystać jako takie (przykład):

String myHtml = "This will display an image to the right <img src='ic_menu_more' />";
myTextview.setText(Html.fromHtml(myHtml, new ImageGetter(), null);
rysować
źródło
Ta kombinacja byłaby idealna przy pobieraniu Internetu przez AsyncTask.
Francis Rodrigues
1
Dzięki! To rozwiązało mój problem. Potrzebuję tylko lokalnych obrazów, więc po prostu upuść je do folderu do rysowania i pamiętaj, aby usunąć rozszerzenie obrazu podczas wywoływania go z html.
Dody Rachmat Wicaksono
Dzięki! Ale uwaga sourcemoże być zerowa i getIdentifier()awarie w tym przypadku. Lepiej dodaj wyraźną kontrolę.
gmk57,
5

Napotkałem ten sam problem i znalazłem całkiem czyste rozwiązanie: po Html.fromHtml () możesz uruchomić AsyncTask, które iteruje po wszystkich tagach, pobiera obrazy, a następnie je wyświetla.

Tutaj znajdziesz kod, którego możesz użyć (ale wymaga to dostosowania): https://gist.github.com/1190397

juliański
źródło
3

Skorzystałem z odpowiedzi Dave'a Webba, ale trochę ją uprościłem. Tak długo, jak identyfikatory zasobów pozostaną takie same podczas działania w Twoim przypadku użycia, tak naprawdę nie ma potrzeby pisania własnej implementacji klasy Html.ImageGetteri bałaganu z łańcuchami źródłowymi.

To, co zrobiłem, polegało na użyciu identyfikatora zasobu jako ciągu źródłowego:

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

i użyj go bezpośrednio:

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);
Czarne światło
źródło
1

Możesz także napisać własny parser, aby pobrać adresy URL wszystkich obrazów, a następnie dynamicznie utworzyć nowe widoki obrazów i przekazać je.

Falmarri
źródło
1

Ponadto, jeśli chcesz dokonać wymiany samodzielnie, postać, której musisz szukać, to [].

Ale jeśli używasz Eclipse, to zwariuje, gdy wpiszesz tę literę do instrukcji [replace], co oznacza, że ​​jest w konflikcie z Cp1252 - to jest błąd Eclipse. Aby to naprawić, przejdź do

Okno -> Preferencje -> Ogólne -> Przestrzeń robocza -> Kodowanie pliku tekstowego,

i wybierz [UTF-8]

pluśnięcie
źródło
0

W przypadku, gdy ktoś uważa, że ​​zasoby muszą być deklaratywne, a używanie Spannable dla wielu języków jest bałaganem, zrobiłem niestandardowy widok

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

śledź aktualizacje, jeśli istnieją, na https://gist.github.com/logcat/64234419a935f1effc67

logcat
źródło
0

KOTLIN

Istnieje również możliwość skorzystania sufficientlysecure.htmltextview.HtmlTextView

Użyj jak poniżej w plikach gradle:

Plik oceny projektu:

repositories {
    jcenter()
}

Plik oceny aplikacji:

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

Wewnątrz pliku xml zamień swój textView na:

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

Ostatni wiersz powyżej dotyczy użycia adapterów Binding, w których kod będzie wyglądał następująco:

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

Więcej informacji ze strony github i wielkie podziękowania dla autorów !!!!!

Farmer
źródło