11

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)
JMP
  • 557
  • 1
  • 6
  • 17

5 Answers5

54

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

pskink
  • 23,874
  • 6
  • 66
  • 77
10

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
Marco RS
  • 8,145
  • 3
  • 37
  • 45
  • 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 context – SmartAndroidian Feb 19 '22 at 14:25
  • 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_crop – Bitwise DEVS Mar 26 '22 at 09:02
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);
        } 
  • Complementing your answer: `final float scale = c.getResources().getDisplayMetrics().density; mDrawable.setBounds(0, 0, Math.round(width * scale) , Math.round(height * scale));` – Emmanouil Nicolas Nov 10 '17 at 18:01
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.

Zeeshan Shabbir
  • 6,704
  • 4
  • 38
  • 74
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).