So I’ve been busy building my own RSS reader for the last few weeks. My motivation to make this app is because I got angry at gReader for displaying fullscreen-ads. The source is available on GitHub.
I started with an idea of targeting Android-L, but because it’s only in preview any app targeting L will be completely incompatible with earler versions. Hence I was forced to refrain from using the new RecyclerView which I really liked. In general I’ve been stealing as much code as possible from the Google-IO app.
It’s early still, but here are two screenshots of current progress:
To parse RSS feeds I have forked Simplistic-RSS by ShirwaM. To display images I am using Picasso by Square (awesome library). I don’t have any intention of uploading this app to the Play store at this time, at least not until I feel that it is fairly stable and feature complete. I am building it all for myself as this is the only kind of app which I actually use everyday. I figure I can talk about the difficulties that I encounter and how to solve them. So today’s topic will be:
Displaying formatted text with images
RSS feeds generally have stories formatted in HTML. For example, see the RSS feed of this blog. This is good because it means all we need to do is decode it and display it. You could use a WebView, but that would be unacceptably ugly and disgusting for an app of mine. A nicer solution is to use a normal TextView. You can actually format HTML easily and display it with:
textview.setText(android.text.Html.fromHtml(htmlString));
This simple act gets you most of the way. Here’s what a story looks like with this:
Notice that in the first image, the image is missing and you don’t see that there is a list in the beginning. In the second image, the source code has no special formatting and it’s hard to tell when it starts or stops.
fromHtml is great, but it is missing functionality to handle some tags. Lucky for us, it is possible to hand it some tagHandlers for those cases. Because I am downloading images, I do the formatting in a background thread using a Loader. To this end I created the ImageTextLoader. What it does instead is:
android.text.Html.fromHtml(text, imageHandler, TagHandler);
Where the imageHandler is really simple (notice that I use Picasso to get the image from the network):
imgThing = new Html.ImageGetter() {
/**
* This methos is called when the HTML parser encounters an
* <img> tag. The <code>source</code> argument is the
* string from the "src" attribute; the return value should be
* a Drawable representation of the image or <code>null</code>
* for a generic replacement image. Make sure you call
* setBounds() on your Drawable if it doesn't already have
* its bounds set.
*
* @param source
*/
@Override
public Drawable getDrawable(final String source) {
Drawable d = null;
try {
final Bitmap b = Picasso.with(appContext).load(source).get();
// Get original size
int w = b.getWidth();
int h = b.getHeight();
// Shrink if big
if (w > maxSize.x || h > maxSize.y) {
Point newSize = scaleImage(w, h);
w = newSize.x;
h = newSize.y;
}
// Need to return a drawable
d = new BitmapDrawable(appContext.getResources(), b);
d.setBounds(0, 0, w, h);
} catch (IOException e) {
Log.e("JONAS", "" + e.getMessage());
}
return d;
}
};
The tag handler contains a bit more code, and I won’t paste all of it here. The tags which are handled can be seen in handleTag:
public void handleTag(final boolean opening, final String tag,
final Editable output, final XMLReader xmlReader) {
if (tag.equalsIgnoreCase("ul")) {
handleUl(output, opening);
} else if (tag.equalsIgnoreCase("ol")) {
handleOl(output, opening);
} else if (tag.equalsIgnoreCase("li")) {
handleLi(output, opening);
} else if (tag.equalsIgnoreCase("img")) {
handleImgEnd(output);
} else if (tag.equalsIgnoreCase("code")) {
handleCode(output, opening);
} else if (tag.equalsIgnoreCase("pre")) {
handlePre(output, opening);
}
}
Note that fromHtml only notifies your handler about img-tags when they have ended, so I use that to insert a newline after each image. I would have liked to use it to get the configured size of the image, but that will have to wait for another day. For code-tags, I reduce the size of the text and make it Monospace:
// Source code
private void handleCode(final Editable text,
final boolean start) {
// Should be monospace
if (start) {
start(text, new Monospace());
start(text, new RelativeSize());
} else {
end(text, Monospace.class,
new TypefaceSpan("monospace"));
end(text, RelativeSize.class,
new RelativeSizeSpan(0.8f));
}
}
The start and end methods were simply stolen straight from android.Html.
Result
Here’s the result using the added tagHandlers:
Handling clicks on links
Thankfully I had already solved the issue of clickable spans in NoNonsense Notes. See [ReaderFragment]() for this:
// Catch clicks on links
mBodyTextView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(final View v,
final MotionEvent event) {
TextView widget = (TextView) v;
Object text = widget.getText();
if (text instanceof Spanned) {
Spanned buffer = (Spanned) text;
int action = event.getAction();
if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link =
buffer.getSpans(off, off, ClickableSpan.class);
// Cant click to the right of a span,
// if the line ends with the span!
if (x > layout.getLineRight(line)) {
// Don't call the span
} else if (link.length != 0) {
link[0].onClick(widget);
return true;
}
}
}
return false;
}
});
Thus clicking on links in the TextView will open them in the browser. You could do whatever you want instead of calling link[0].onClick() however.
That’s it for today. I’ll write more about other pieces of the app soon. Things like how the database is structured or how to use ExpandableListView.