Tuesday, February 26, 2013

Adding images to TextView and EditText using Spannables in Mono for Android

I've just been playing around with Spannables in an attempt to help out a user on the Xamarin Forums, his topic is "Text With Image". Just from looking at that topic name, I thought: "Hey, he probably just want to show an image next to some text or something, this should be easy enough". Then I looked inside, and they guy actually wanted text inline with text when actually editing it. Now I thought: "How do I do that?!"
I think I have touched the concept of Spannables briefly before, when I helped a guy making a sub-part of a text bold or something, but I never thought they could be used for displaying images inline with text. I guess this is how it is done in all those messaging apps, which show emoticons. I decided to use that as a sample.

I started out with a super simple sample trying to get an Image shown in a TextView. I googled up some solutions and sure, Spannables allow using ImageSpan inside of them! There were nice samples and such, and I came up with this.
Easy, huh? And you can add loads of other Spans to the Spannable, such as StyleSpan, which you can style your fonts with bold, italic and other styles.

Since that was so easy, I quickly tried to do that with an EditText. It also works just fine, so moving on. How do I imitate those messaging apps, which replace ":-)" with an actual smiley. Now EditText has some nice events which you can listen to for changes. I listen for AfterTextChanged, from here I can get the Editable Property from the argument, which is essentially a Spannable you can edit and the changes will be reflected in the EditText. This is probably a good place to replace text with images.
Next I cooked up a class which holds a Dictionary of strings and ints. The string keys are the text representation of the smileys, i.e. :), :-), :D, :P and so on. The ints are the Android Resource ids for the Drawables representing the smileys. Then I have a method for replacing all the matches in the string with their respective Drawables. It looks like this.
Now you might notice I am removing all the images if they are present. This is because it is not possible to have two Spans at the same index in a Spannable, hence they need to be removed. Don't worry all the images are added at the end. This simply loops all the string matches in the Spannable and adds ImageSpans for the matches. Very simple. To get all the indices in a string which match I use a simple extension I found on StackOverflow.
Now the AddSmiles() method is hooked up to the AfterTextChanged event for the EditText, and voila. Text is replaced with images!
Pretty awesome smileys :-D 
The complete sample code can be found in this Gist: https://gist.github.com/Cheesebaron/5034440

You can also watch this video for a demonstration