Tuesday, June 8, 2010

TextView with HTML content with Images

Handling HTML content on a TextView is simple as far as the HTML coming in contains a few tags that are by default supported by Android. Simple formatting like bold, italics, font sizes can be handled without even coding a single extra line.

Say, if you have a TextView tv, and there's some HTML string with bold and italicized text, bringing it up on the TextView is pretty simple.

One line code for that:

tv.setText(Html.fromHtml(source)); 

where the source is actually your HTML string. This works perfectly. But how do we show images if there are any. Well, it's a bit tricky. You have to use the other method that takes in an ImageGetter and a TagHandler.

fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler)

The tagHandler is for situations where you wish to handle specific tags differently. I didn't wish to do that, so I just passed null there.

Now comes the main task. How do you get the image on to the TextView!!!!

Implement the Html.ImageGetter's getDrawable method which handles downloading the image, or accessing it from the net, and then create a drawable and return that object.

      static ImageGetter imgGetter = new Html.ImageGetter() {
             @Override
             public Drawable getDrawable(String source) {
                   Drawable drawable = null;
                   drawable = Drawable.createFromPath(source);  // Or fetch it from the URL
                   // Important
                   drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable
                                 .getIntrinsicHeight());
                   return drawable;
             }
      };

and use the method on the TextView like this.
tv.setText(Html.fromHtml(source, imgGetter, null);

This will load the TextView with the image. But this call to the getDrawable method is not asynchronous. So, until and unless that method returns, you UI will be blocked. In my case, I am creating the drawable from a local image, so, it didn't take much time. But, if you want to fetch an image from the web, you have to make this call in a separate thread, so that the UI is not blocked.

So, check your HTML string if they contain any images that have to be downloaded. If you find any, create a thread that download that image, saves it somewhere and returns you the location of that file. Now, change the src tags to point to the local images, and call setText method on the TextView.

And that should do it. The important thing to remember is, you have to change the HTML to point it to the file that you have downloaded.

Sample Source code : http://code.google.com/p/myandroidwidgets/source/browse/#svn/trunk/TextViewHTML 

This sample doesn't use threads. So, your UI will be blocked unitl the image here is downloaded. So, keep waiting. :)

52 comments:

  1. Hi kumar,

    drawable = Drawable.createFromPath(source);
    This lines return null..
    wat's the problem with this ?..

    ReplyDelete
  2. Kumar,

    Some Text in my Paragraph Tag
    < src="http://google.com/media/sample123.jpg" alt="" />

    When i try to post the comment. Its not accepting paragraph & image tag. So i removed those tags.

    Here

    This is my source. When i try to get my source today morning its works fine. But yesterday it showed a null pointer exception in setBounds line.

    What's the problem ?..

    ReplyDelete
  3. Kumar,

    In my last comment i mentioned its working fine. After i posted comment & i tried to execute one more time, it showed same error Null Pointer Exception in setBound Line...

    Actually the drawable returns as null, so we can't able to set Bounds for that image.

    What's wrong with my code ?...

    ReplyDelete
  4. The source should be a local file. Else, you have to in this method, download that file from the net, save it locally, and return the drawable of that file.

    ReplyDelete
  5. Thanks for your reply Kumar.
    I'm fetching from server (using JSON ) and then storing it to DB.
    From DB only i'm setting it to textview.

    we should not use source from DB too ?..

    ReplyDelete
  6. I haven't checked if it works that way, but with the file stuff, it works. If you an give some code, then I can check it and let you know.

    Mail it to me...

    ReplyDelete
  7. Once again thanks for your reply.
    I will send the file in couple of hours to your mail id. Sorry to take couple of hours

    ReplyDelete
  8. By, code, I meant, Java code :)
    What I can guess, would be your problem, is the formatting. Re-creating an image from a byte stream is difficult. So, I would suggest that you create files and use those files to create drawables.

    ReplyDelete
  9. Thanks Kumar,
    Let me check it & then you know..

    ReplyDelete
  10. Hi Kumar,
    When i try to use the code. I got the exception.
    Exception = java.io.FileNotFoundException: /sdcard/test.jpg

    When i created Emulator i didn't mentioned any size of SD Card. In my android Phone also i'm not having SD Card.
    This problem is due to SD Card ? , If so please give another idea without SD Card to load image in textview..

    ReplyDelete
  11. Use the data directory.

    Environment.getExternalStorageDirectory(). Instead of this, use Environment.getDataDirectory()

    ReplyDelete
  12. When i try to use getDataDirectory() it throws Excpetion.
    Exception = java.io.FileNotFoundException: /data/test.jpg

    I changed getDataDirectory() in both places.

    FileOutputStream fileout = new FileOutputStream(new File(Environment.getDataDirectory().getAbsolutePath()+ "/test.jpg"));

    drawable = Drawable.createFromPath(Environment .getDataDirectory().getAbsolutePath() + "/test.jpg");

    ReplyDelete
  13. Well, I guess, you can easily check and rectify this error. Else, please setup an SD-card. I am sure you can get around this problem

    ReplyDelete
  14. For past 2 days i tried to find that solution. But i can't .. Can you please help over from this ?..

    ReplyDelete
  15. Please post some code where you have a problem.

    ReplyDelete
  16. Problem arise at the line

    drawable = Drawable.createFromPath(Environment .getDataDirectory().getAbsolutePath() + "/test.jpg");

    Before this line... I set

    FileOutputStream fileout = new FileOutputStream(new File(Environment.getDataDirectory().getAbsolutePath()+ "/test.jpg"));

    ReplyDelete
  17. Environment.getDataDirectory().getAbsolutePath()+"/data/your package"+"/test.jpg"

    You have to save the file inside /data/your package/file name

    ReplyDelete
  18. Kumar... Thanks for your reply...
    I replaced my code with your code.
    Previously it thrown File not found exception... Now its throwing Unknown host exception.. I think its due to net problem ( Net speed is very low here). Tomorrow i will check it one more time & then i will reply to you about this..

    Once again thanks for your reply & answer.. :)

    ReplyDelete
  19. Thanks Kumar :)

    Its working fine.

    ReplyDelete
  20. Hi Kumar,
    Your code works fine. But when tried running it in thread it throws this exception

    "Exception=android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views."

    Kindly give some hint what causing it.

    ReplyDelete
  21. Well, you cannot access UI from a different thread. Try using the handler.

    ReplyDelete
  22. I can not use color for my text when I am using Html.fromHtml(source)
    can you help me, please?

    ReplyDelete
  23. thanks for your post.I'm trying to use Html.fromHtml, but I can not apply color for my text in textView, and I dont know how to insert image from local like you've said. Could you give me an example to solve two problems here, please?

    ReplyDelete
  24. Hello Kumar,

    I was just wondering if you have any idea about my problem. When I use getExternalStorageDirectory() it is able to create a temporary file but if I replaced it with getDataDirectory() I am able to retrieve the path but an error occurs when I create the
    temporary file. Can you help me with this? Thank you so much..

    File sampleDir = Environment.getExternalStorageDirectory();

    try {
    audiofile = File.createTempFile("test", ".3gp", sampleDir);
    }

    ReplyDelete
  25. public static File getDataDirectory ()
    Since: API Level 1

    Gets the Android data directory.

    For access to your package's data directory, you have to append "/data/" to this path.

    ReplyDelete
  26. Hello long,

    Here is a solution to set color for a text in Html.fromHtml()

    http://www.androidpeople.com/android-html-view/

    ReplyDelete
  27. Hello Kumar,

    Thanks for your quick reply..

    > For access to your package's data directory,
    > you have to append "/data/" to this path.

    File sampleDir = Environment.getDataDirectory();

    try {
    audiofile = File.createTempFile("test", ".3gp", sampleDir);
    }

    upon checking sampleDir is already equal to "/data" , do I still need to append "/data/" when calling createTempFile?

    If yes, I tried passing the following arguments instead
    File.createTempFile("/data/test", ".3gp", sampleDir)

    and it still didn't work..

    upon checking both values of AudioFile is null..

    Sorry, but I'm still new to Android programming..
    Thanks again.. Hope you can help me on this one.

    ReplyDelete
  28. You data directory would be /data/data/. This is the complete path to your packages data directory. So, yes, you will have to not only append /data, but also your package name. For example, if your package name is com.sample, then your code should be like this.

    File file = new File(Environment.getDataDirectory().getAbsolutePath()+"/data/com.sample/test.jpg");

    Try this, this should work.

    ReplyDelete
  29. Thanks so much, now I'm using webview to show picture and rich text data. But it turns out to be very slow whenever I append text, and reload all the html content. I still want to use textView with image, for example chat with emoticon, hix, how to do that?
    Hope you can help me.

    ReplyDelete
  30. Well, emoticons are different. Except those, you can show any HTML content on a TextView, of course, except videos. I have not done a performance comparison between the two, so I can't really comment if you are better off using TextView instead of WebView. You can ask your question on google groups, and someone will answer for sure.

    ReplyDelete
  31. Ok, thanks, using webview is the worst method I use to display image and text in a view, because I must reload all the data whenever I add new content. So that, thanks, I'm still finding out a way to display text and image. Thanks so much:D

    ReplyDelete
  32. Hello Kumar,

    Thank you very much!!

    Now I understand how it works. And also the code worked..
    >File file = new >File(Environment.getDataDirectory().getAbsolutePath()+"/data/com.sample/test.jpg");

    Thank you again..

    ReplyDelete
  33. Hi Kumar,
    I want to get path of a file "accounts.db" which exists at path "/data/system/accounts.db".

    can you please suggest how should i use getDataDirecoty() in this case.
    i can hardcode till "/data" after that i want any function wich would search this file.

    any help wud be appreciable.

    ReplyDelete
  34. You cannot access system files like this. Your apps will not have access to these folders (system). Sorry.

    ReplyDelete
  35. Hello, I tried with your code but it displayed only source in the text view! No image is displayed. why it is happened?

    Thanks.

    ReplyDelete
  36. Did you try running the example? If the example works, then probably you are doing something wrong. Do let me know the errors from the Logcat.

    ReplyDelete
  37. Now Only a blue icon image is coming. Not the real image.

    ReplyDelete
  38. java.io.FileNOTFountException: /mnt/sdcard/test.jif (Permission denied)

    ReplyDelete
  39. yeh .. it is working..
    I have used
    "Environment.getDataDirectory().getAbsolutePath()+"/data/com.mypackage/test.jpg"

    Thanks a lot....

    ReplyDelete
  40. Hi Kumar,

    It's working fine with TextView - but i would like to populate image in email body - any pointer on this would be greatly appriciated.

    Thanks
    Ankit Shah

    ReplyDelete
  41. you don't have to save the image locally first, you can use Drawable.createFromStream(inputStream, source) method, i have tried it, it works ! :D :D :D

    ReplyDelete
  42. @m Aji: Thanks. That's already mentioned though in the post. For the example, I chose to download and save it before using it. :)

    ReplyDelete
  43. how can i send image in email body in android

    ReplyDelete
  44. Hi Bibek
    I need to insert and image in textview and I need to access the image locally from res/raw directory. Can you explain?

    ReplyDelete
  45. Your getDrawable method should return the required drawable. So, it doesn't really depend on how you create it, it can be from your resources as well.

    ReplyDelete
  46. Hi.I tried using this in an edit text but encountered the following problems
    1)When i try to enter a second image the first disappears.
    2)The focus returns to the start of the edit text after inserting the image

    ReplyDelete
  47. How to create contact group and set ringtone for it in android. Help me

    ReplyDelete
  48. What if i want to load multiple images and format them alternating with text for example
    this is image 1:

    this is image 2:




    thanks

    ReplyDelete