Html.ImageGetter TextView
Asked Answered
C

5

12

So Im using ImageGetter to display the images from JSON blog posts. Im getting the correct source in the log but the URL changes when it reaches setBounds. Any ideas?

Code:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_blog_view);

    Intent intent = getIntent();
    Uri blogUri = intent.getData();
    mPost = blogUri.toString();
    mUrl = getIntent().getStringExtra("mUrl");

    TextView textView = (TextView) findViewById(R.id.scrollView1);
    textView.setText(Html.fromHtml(mPost, imgGetter, null));
}

private ImageGetter imgGetter = new ImageGetter(){
    @Override
    public Drawable getDrawable(String source){
         Drawable drawable = Drawable.createFromPath(source);
        try {
            drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        }catch (NullPointerException e){
            logException(e);
        }
        return drawable;
    }
};

The "source" before the try is

http://www.domain.com/images_blog/feature.png

but in the catch the error is:

Unable to decode stream:

java.io.FileNotFoundException: /http:/www.domain.com/images_blog/feature.png : open failed: ENOENT (No such file or directory)
Chondriosome answered 23/4, 2013 at 21:13 Comment(5)
carefully read the docs for Drawable.createFromPath()Maritamaritain
So I assume you are saying I should use Drawable.createFromStream(), correct?Chondriosome
yes, but also you could create some kind of cache and then createFromPath could be usedMaritamaritain
Do you have an example or tut that explains that process?Chondriosome
use one of many async download libraries e.g. github.com/zonghai-li/android-http-image-managerMaritamaritain
M
55

the easiest solution is:

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

import org.pskink.soom.R;

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 TestImageGetter 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.test_image_getter);
        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://developer.android.com/assets/images/icon_search.png\">";

        Spanned spanned = Html.fromHtml(source, 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);
            }
        }
    }
}

there is one not quite elegant way to re-layout a TextView after image downloading:

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

if anybody knows better solution please let me know

Maritamaritain answered 25/4, 2013 at 8:17 Comment(13)
mTv.refreshDrawableState() method help you. Call this method to force a view to update its drawable state.Septavalent
@Maritamaritain can you please guide me how to center the image that i get from server? please that would mean a lot.Striking
@ZeeshanShabbir center? center relative to what? the entire text?Maritamaritain
I want to center the image in between text. Apparent it is align to left. I don't know to explain it to you. Can you please look into this link dev.propakistani.pk/2016/01/11/… That's how exactly i want to images in text. centered without stretching themStriking
@Maritamaritain some guidelines would be appreciated. PleaseStriking
@ZeeshanShabbir i have no idea what you want to achieveMaritamaritain
I just want to center the image that within textview. By deault textview's text is left align so when i used your great code within my project i get image with text but images are left aligned. I want to align them in center.Striking
@ZeeshanShabbir post two images: what you get now and what you wantMaritamaritain
Let us continue this discussion in chat.Striking
saved lots of hours..ThanksEmulsion
@Maritamaritain Is there any way to Set ViewPager onto these images?Callisto
@Callisto what ViewPager?Maritamaritain
I got stuck with the issue ::: The inline style not applied , like style=color:red;"Barquisimeto
L
11

An alternative solution using Glide and Coroutines with the assumption that a retry is not required:

import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.text.Html
import android.widget.TextView
import androidx.lifecycle.LifecycleCoroutineScope
import com.bumptech.glide.RequestManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt

class HtmlImageGetter(
    private val scope: LifecycleCoroutineScope,
    private val res: Resources,
    private val glide: RequestManager,
    private val htmlTextView: TextView
) : Html.ImageGetter {

    override fun getDrawable(url: String): Drawable {
        val holder = BitmapDrawablePlaceHolder(res, null)

        scope.launch(Dispatchers.IO) {
            runCatching {
                val bitmap = glide
                    .asBitmap()
                    .load(url)
                    .submit()
                    .get()

                val drawable = BitmapDrawable(res, bitmap)

                val scale = 1.25 // This makes the image scale in size.
                val width = (drawable.intrinsicWidth * scale).roundToInt()
                val height = (drawable.intrinsicHeight * scale).roundToInt()
                drawable.setBounds(0, 0, width, height)

                holder.setDrawable(drawable)
                holder.setBounds(0, 0, width, height)

                withContext(Dispatchers.Main) { htmlTextView.text = htmlTextView.text }
            }
        }

        return holder
    }

    internal class BitmapDrawablePlaceHolder(res: Resources, bitmap: Bitmap?) : BitmapDrawable(res, bitmap) {
        private var drawable: Drawable? = null

        override fun draw(canvas: Canvas) {
            drawable?.run { draw(canvas) }
        }

        fun setDrawable(drawable: Drawable) {
            this.drawable = drawable
        }
    }
}

In a Fragment or Activity use with HtmlCompat

val imageGetter = HtmlImageGetter(lifecycleScope, resources, glide, htmlTextView)
val styledText = HtmlCompat.fromHtml(htmlString, flags, imageGetter, null)
htmlTextView.text = styledText
Lair answered 25/9, 2019 at 5:9 Comment(2)
Thanks @Marco this solution helped me a lot. I like to add one point, for requestManager, we can get through val glide = Glide.with(this). Here "this" is basically the contextInbreed
Tried to add a Base64 support using your code but the generated bitmap is not occupying the whole screen width. Is there any way we can make this behave like scale to center_cropEndocentric
R
3

If you have mTV (I mean TextView), you can calculate based on its dimensions (mTv.getWidth() and mTv.getHeight()) and dimension of created bitmap (bitmap.getWidth() and bitmap.getHeight()) and set these values as new dimensions of TextView (mTv).

        if (bitmap != null) {
            BitmapDrawable d = new BitmapDrawable(bitmap);
            mDrawable.addLevel(1, 1, d);
            int width = mTv.getWidth() < bitmap.getWidth() ? mTv.getWidth() : bitmap.getWidth();
            int height = bitmap.getHeight() * width / bitmap.getWidth();
            mDrawable.setBounds(0, 0, width, height);
            mDrawable.setLevel(1);
            // i don't know yet a better way to refresh TextView 
            // mTv.invalidate() doesn't work as expected 
            // but we can calculate new TextView dimensions
            mTv.setHeight(height);
            CharSequence t = mTv.getText();
            mTv.setText(t);
        } 
Renteria answered 3/10, 2013 at 13:47 Comment(1)
Complementing your answer: final float scale = c.getResources().getDisplayMetrics().density; mDrawable.setBounds(0, 0, Math.round(width * scale) , Math.round(height * scale));Defection
S
1

This answer may help someone. I used Jsoup to extract <Img/> tag out of the string then i show the image in ImageView and <p> in Textview. Results was according to what i needed. Also i used Universal Image Loader Libaray to load images in ImageView Then i added view programmatically to the layout in my case layout was the linearlayout so i made a helper class and passed content,html string and linear layout as the parameter.

add jsoup in your project.

compile 'org.jsoup:jsoup:1.9.2'

Here some snippet.

public class PostContentHandler {
Context context;
String content;
LinearLayout linearLayout;

public PostContentHandler(Context context, String content, LinearLayout linearLayout) {
    this.context = context;
    this.content = content;
    this.linearLayout = linearLayout;

}

public void setContentToView() {

    //custom font
    Typeface bitterBoldFont = Typeface.createFromAsset(context.getAssets(), "fonts/Bitter-Regular.otf");

    List<String> p = new ArrayList<>();
    List<String> src = new ArrayList<>();
    List<String> li = new ArrayList<>();
    Document doc = Jsoup.parse(content);

    Elements elements = doc.getAllElements();

    for (Element element : elements) {
        Tag tag = element.tag();
        if (tag.getName().matches("h[1-6]{1}")) {
            String heading = element.select(tag.getName().toString()).text();
            TextView textView = new TextView(context);
            textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) textView.getLayoutParams();
            int top = (int) context.getResources().getDimension(R.dimen.heading_margin_top);
            int start = (int) context.getResources().getDimension(R.dimen.content_margin);
            int end = (int) context.getResources().getDimension(R.dimen.content_margin);
            params.setMargins(start, top, end, 0);
            textView.setTextSize(20);
            textView.setTypeface(bitterBoldFont);
            textView.setText(heading);
            textView.setTextColor(context.getResources().getColor(R.color.black));
            linearLayout.addView(textView);
        }
 if (tag.getName().equalsIgnoreCase("p")) {
            element.select("img").remove();
            String body = element.html();
            TextView textView = new TextView(context);
            textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) textView.getLayoutParams();
            int top = (int) context.getResources().getDimension(R.dimen.heading_margin_top);
            int start = (int) context.getResources().getDimension(R.dimen.content_margin);
            int end = (int) context.getResources().getDimension(R.dimen.content_margin);
            params.setMargins(start, top, end, 0);
            textView.setTypeface(bitterBoldFont);
            textView.setLinksClickable(true);
            textView.setAutoLinkMask(Linkify.WEB_URLS);
            textView.setText(Html.fromHtml(body));
            textView.setTextColor(context.getResources().getColor(R.color.content_color));
            linearLayout.addView(textView);
            p.add(body);
        }
        if (tag.getName().equalsIgnoreCase("ol")) {
            String ol = element.select(tag.getName().toString()).outerHtml();
            TextView textView = new TextView(context);
            textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) textView.getLayoutParams();
            params.setMarginStart((int) context.getResources().getDimension(R.dimen.content_margin));
            int top = (int) context.getResources().getDimension(R.dimen.heading_margin_top);
            int start = (int) context.getResources().getDimension(R.dimen.content_margin);
            int end = (int) context.getResources().getDimension(R.dimen.content_margin);
            params.setMargins(start, top, end, 0);
            textView.setTypeface(bitterBoldFont);
            textView.setLinksClickable(true);
            textView.setAutoLinkMask(Linkify.WEB_URLS);
            textView.setTextColor(context.getResources().getColor(R.color.content_color));
            textView.setText(Html.fromHtml(ol, null, new MyTagHandler()));
            linearLayout.addView(textView);

        }
        if (tag.getName().equalsIgnoreCase("ul")) {
            String ul = element.select(tag.getName().toString()).outerHtml();
            TextView textView = new TextView(context);

            textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) textView.getLayoutParams();
            int top = (int) context.getResources().getDimension(R.dimen.heading_margin_top);
            int start = (int) context.getResources().getDimension(R.dimen.content_margin);
            int end = (int) context.getResources().getDimension(R.dimen.content_margin);
            params.setMargins(start, top, end, 0);
            textView.setTypeface(bitterBoldFont);
            textView.setLinksClickable(true);
            textView.setAutoLinkMask(Linkify.WEB_URLS);
            textView.setTextColor(context.getResources().getColor(R.color.content_color));
            textView.setText(Html.fromHtml(ul, null, new MyTagHandler()));
            linearLayout.addView(textView);
        }
        if (tag.getName().equalsIgnoreCase("img")) {
            String url = element.select("img").attr("src");
            final ImageView imageView = new ImageView(context);
            imageView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            final ProgressBar progressBar = new ProgressBar(context);
            linearLayout.addView(progressBar);
            progressBar.setVisibility(View.GONE);
            ImageLoader imageLoader = ImageLoader.getInstance();
            imageLoader.displayImage(url, imageView, new SimpleImageLoadingListener() {
                @Override
                public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                    super.onLoadingComplete(imageUri, view, loadedImage);
                    progressBar.setVisibility(View.INVISIBLE);
                    int height = loadedImage.getHeight();
                    imageView.getLayoutParams().height = getScreenWidth();
                    imageView.setAdjustViewBounds(true);
                    imageView.requestLayout();
                }

                @Override
                public void onLoadingStarted(String imageUri, View view) {
                    super.onLoadingStarted(imageUri, view);
                    progressBar.setVisibility(View.VISIBLE);
                }
            });
            linearLayout.addView(imageView);
            src.add(url);
        }

    }
}

public static int getScreenWidth() {
    return Resources.getSystem().getDisplayMetrics().widthPixels;
}
}

I hope my answer will help someone.

Striking answered 19/5, 2016 at 7:29 Comment(0)
S
0

If you have mTV (I mean TextView), you can calculate based on its dimensions (mTv.getWidth() and mTv.getHeight()) and dimension of created bitmap (bitmap.getWidth() and bitmap.getHeight()) and set these values as new dimensions of TextView (mTv).

Susurrant answered 7/9, 2020 at 16:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.