Image Processing – Draw Text on a Curve
Just watching on StackOverflow and found this interesting question: How to write curve text?
Here what I’ve tried so far:
Here my implementation:
package pete.android.study; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.view.View; public class GraphicsView extends View { private static final String MY_TEXT = "xjaphx: Draw Text on Curve"; private Path mArc; private Paint mPaintText; public GraphicsView(Context context) { super(context); mArc = new Path(); RectF oval = new RectF(50,100,200,250);; mArc.addArc(oval, -180, 200); mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintText.setStyle(Paint.Style.FILL_AND_STROKE); mPaintText.setColor(Color.WHITE); mPaintText.setTextSize(20f); } @Override protected void onDraw(Canvas canvas) { canvas.drawTextOnPath(MY_TEXT, mArc, 0, 20, mPaintText); invalidate(); } }
Enjoy painting Android 🙂
Cheers,
Pete Houston
Android XML Adventure – Parsing XML Data with SAXParser
Article Series: Android XML Adventure
Author: Pete Houston (aka. `xjaphx`)
TABLE OF CONTENTS
- What is the “Thing” called XML?
- Parsing XML Data w/ SAXParser
- Parsing XML Data w/ DOMParser
- Parsing XML Data w/ XMLPullParser
- Create & Write XML Data
- Compare: XML Parsers
- Parsing XML using XPath
- Parsing HTML using HtmlCleaner
- Parsing HTML using JSoup
- Sample Project 1: RSS Parser – using SAXParser
- Sample Project 1: RSS Parser – using DOM Parser
- Sample Project 1: RSS Parser – using XMLPullParser
- Sample Project 2: HTML Parser – using HtmlCleaner
- Sample Project 2: HTML Parser – using JSoup
- Finalization on the “Thing” called XML!
=========================================
Hope you’ve already known what XML is in previous article `What is the “Thing” called XML?`.
At the start of the series, I will talk about how to parse XML data. Why parsing first? Well, it’s because most of the applications tend to parse XML data from other sources, like RSS, which is very common.
There are many ways to parse XML file in Android, however, the most three common methods are:
I’ll talk about SAXParser in this article.
For a quick and easy understandings, I’ve created this flow-chart to describe how SAX-Parser works.
All the elements in XML document will be parsed through a ContentHandler with five pre-defined methods handling actions taken.
Class Overview
This is the main interface that most SAX applications implement: if the application needs to be informed of basic parsing events, it implements this interface and registers an instance with the SAX parser using the setContentHandler
method. The parser uses the instance to report basic document-related events like the start and end of elements and character data.
The order of events in this interface is very important, and mirrors the order of information in the document itself. For example, all of an element’s content (character data, processing instructions, and/or subelements) will appear, in order, between the startElement event and the corresponding endElement event.
This interface is similar to the now-deprecated SAX 1.0 DocumentHandler interface, but it adds support for Namespaces and for reporting skipped entities (in non-validating XML processors).
(@Quote from: http://developer.android.com/reference/org/xml/sax/ContentHandler.html)
I guess the picture I drew above explains everything. Let’s head to work.
First, we need a sample XML file, I called it: “record.xml” and put it under “assets” directory, using as assets.
<?xml version="1.0" encoding="UTF-8"?> <record> <study id="1"> <topic>SAX Parser</topic> <content>Learn how to parse XML using SAXParser</content> <author>Pete Houston</author> <date>02-Oct-2011</date> </study> </record>
We will use SAX-Parser to parse these data and display on a TextView.
The data we need: [ study id, topic, content, author, date ], I store them as an entity called “Study“, `Study.java`.
package pete.android.study.data; public class Study { public int mId; public String mTopic; public String mContent; public String mAuthor; public String mDate; public static final String STUDY = "study"; public static final String ID = "id"; public static final String TOPIC = "topic"; public static final String CONTENT = "content"; public static final String AUTHOR = "author"; public static final String DATE = "date";}
Great, we’ve done half of the job. Next, we need a ContentHandler that does the job of parsing every single elements in XML document, called it “StudyHandler.java“:
package pete.android.study.parser; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import pete.android.study.data.Study; /* * class StudyHandler * */ public class StudyHandler extends DefaultHandler { // members private boolean isTopic; private boolean isContent; private boolean isAuthor; private boolean isDate; // 'Study' entity to parse private Study mStudy; // 'getter' is enough public Study getStudy() { return mStudy; } /* * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#startDocument() */ @Override public void startDocument() throws SAXException { // create new object mStudy = new Study(); } /* * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#endDocument() */ @Override public void endDocument() throws SAXException { // nothing we need to do here } /* * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) */ @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { // if this element value equals "study" if(localName.equals(Study.STUDY)) { // get id right away mStudy.mId = Integer.parseInt(atts.getValue(Study.ID)); } // if this element value equals "topic" else if(localName.equals(Study.TOPIC)) { // mark current element is "topic" isTopic = true; } // if this element value equals "content" else if(localName.equals(Study.CONTENT)) { // mark current element is "content" isContent = true; } // if this element value equals "author" else if(localName.equals(Study.AUTHOR)) { // mark current element is "author" isAuthor = true; } // if this element value equals "date" else if(localName.equals(Study.DATE)) { // mark current element is "date" isDate = true; } } /* * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) */ @Override public void endElement(String namespaceURI, String localName, String qName) throws SAXException { if(localName.equals(Study.STUDY)) { // already get the attribute "id", nothing needs to do here } // if this element value equals "topic" else if(localName.equals(Study.TOPIC)) { // uncheck marking isTopic = false; } // if this element value equals "topic" else if(localName.equals(Study.CONTENT)) { // uncheck marking isContent = false; } // if this element value equals "topic" else if(localName.equals(Study.AUTHOR)) { // uncheck marking isAuthor = false; } // if this element value equals "topic" else if(localName.equals(Study.DATE)) { // uncheck marking isDate = false; } } /* * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) */ @Override public void characters(char ch[], int start, int length) { // get all text value inside the element tag String chars = new String(ch, start, length); chars = chars.trim(); // remove all white-space characters // if this tag is "topic", set "topic" value if(isTopic) mStudy.mTopic = chars; // if this tag is "content", set "content" value else if(isContent) mStudy.mContent = chars; // if this tag is "author", set "author" value else if(isAuthor) mStudy.mAuthor = chars; // if this tag is "date", set "date" value else if(isDate) mStudy.mDate = chars; } }
There we go a data handler done. Basically, that’s enough; however, for simplicity, I’d like to create an utility class that handles the whole parsing stuffs, called “StudyParser.java“:
package pete.android.study.parser; import java.io.InputStream; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import android.util.Log; import pete.android.study.data.Study; public class StudyParser { public static Study parse(InputStream is) { Study study = null; try { // create a XMLReader from SAXParser XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); // create a StudyHandler too StudyHandler studyHandler = new StudyHandler(); // apply handler to the XMLReader xmlReader.setContentHandler(studyHandler); // the process starts xmlReader.parse(new InputSource(is)); // get the target `Study` study = studyHandler.getStudy(); } catch(Exception ex) { Log.d("XML", "StudyParser: parse() failed"); } // return Study we found return study; } }
OK! So in main program, just giving a static call to “StudyParser.parse()” will return us a “Study” object from input XML.
Here my main program, you can just create any program you like. Mine having a TextView to display result is so enough for demonstration.
package pete.android.study; import java.io.IOException; import pete.android.study.data.Study; import pete.android.study.parser.StudyParser; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; public class Main extends Activity { TextView tvStudy; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tvStudy = (TextView)findViewById(R.id.text); Study study = null; try { study = StudyParser.parse(getAssets().open("record.xml")); } catch (IOException e) { Log.d("XML","onCreate(): parse() failed"); return; } String output = ""; output += "Study ID: " + study.mId + "\n"; output += "Topic: " + study.mTopic + "\n"; output += "Content: " + study.mContent + "\n"; output += "Author: " + study.mAuthor + "\n"; output += "Date: " + study.mDate + "\n"; tvStudy.setText(output); } }
And this is the output result:
The data displays as expected!
Simple enough with SAX-Parser?
If you’re beginner or newbie, just read this article slowly and don’t skim, and you will understand it.
There are still more about SAX-Parser, however, I just guide you the most common usage and the most simple concept of parsing XML data using SAX-Parser. You will find more by Googling, I believe!
See you in the next article!!!
Cheers,
Pete Houston
Store and use files in Assets
There are times when you probably want to your application distribution with raw resources, instead of pre-defined resources, the ‘res‘ folder, you gonna have to make use of ‘Asset‘.
‘Assets’ folder will be distributed along with the APK, which contains all the raw files you need for application, such as: text files (.txt), non-Android XML files (.xml), Audio files (.wav, .mp3, .mid)…; those cannot be put into ‘res‘ folder as usual.
Thing needed to be looked up here: AssetManager from Android Developers’ References
This class does the job that we need.
First, create a project as usual, then put files into ‘asset‘ like below:
Now, create a simple layout containing a TextView for displaying the content of ‘text.txt‘ and an ImageView for displaying the image ‘avatar.jpg’, which are put in Asset.
The implementation quite easy using AssetManager as mentioned above.
package pete.android.study; import java.io.IOException; import java.io.InputStream; import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.widget.ImageView; import android.widget.TextView; public class Main extends Activity { ImageView mImage; TextView mText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mImage = (ImageView)findViewById(R.id.image); mText = (TextView)findViewById(R.id.text); loadDataFromAsset(); } public void loadDataFromAsset() { // load text try { // get input stream for text InputStream is = getAssets().open("text.txt"); // check size int size = is.available(); // create buffer for IO byte[] buffer = new byte[size]; // get data to buffer is.read(buffer); // close stream is.close(); // set result to TextView mText.setText(new String(buffer)); } catch (IOException ex) { return; } // load image try { // get input stream InputStream ims = getAssets().open("avatar.jpg"); // load image as Drawable Drawable d = Drawable.createFromStream(ims, null); // set image to ImageView mImage.setImageDrawable(d); } catch(IOException ex) { return; } } }
That’s a quick sample code giving this result.
The distributed APK file contains the folder ‘assets‘, you might wanna check by opening it.
Quite easy, isn’t it?
@p/s: you can load image from Asset into Bitmap by using BitmapFactory.decodeStream(), instead of using Drawable.
Have fun w/ Android Coding 🙂
Cheers,
Pete Houston