Is there a way to create a custom XML data type for Android?
I have a class Model
that contains all of the statistics of my entities. I want to be able to inflate Model
class from xml similar - well, exaclty as View's do. Is this possible?
Example:
<?xml version="1.0" encoding="utf-8"?>
<models xmlns:android="http://schemas.android.com/apk/res/android">
<model name="tall_model"
type="@string/infantry"
stat_attack="5"
>Tall Gunner</model>
<model name="short_model"
type="@string/infantry"
stat_attack="3"
ability="@resource/scout"
>Short Gunner</model>
<model name="big_tank"
type="@string/vehicle"
stat_attack="7"
armour="5"
>Big Tank</model>
</models>
And the class I would like to inflate.
class Model [extends Object] {
public Model(Context context, AttributeSet attrs) {
// I think you understand what happens here.
}
// ...
}
With some custom code using carefully selected APIs, you can mimic the way Android inflates layout XML files and still benefit from the XML optimizations and goodies that Android has like compiled XML files and references to arbitrary resources within your custom XML files. You can't directly hook in into the existing LayoutInflater
because that class can only deal with inflating View
s. In order for the below code to work, put your XML file in 'res/xml' in your application.
First, here's the code that parses the (compiled) XML file and invokes the Model
constructor. You might want to add some registering mechanism so that you can easily register a class for any tag, or you might want to use ClassLoader.loadClass()
so that you can load classes based on their name.
public class CustomInflator {
public static ArrayList<Model> inflate(Context context, int xmlFileResId) throws Exception {
ArrayList<Model> models = new ArrayList<Model>();
XmlResourceParser parser = context.getResources().getXml(R.xml.models);
Model currentModel = null;
int token;
while ((token = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (token == XmlPullParser.START_TAG) {
if ("model".equals(parser.getName())) {
// You can retrieve the class in other ways if you wish
Class<?> clazz = Model.class;
Class<?>[] params = new Class[] { Context.class, AttributeSet.class };
Constructor<?> constructor = clazz.getConstructor(params);
currentModel = (Model)constructor.newInstance(context, parser);
models.add(currentModel);
}
} else if (token == XmlPullParser.TEXT) {
if (currentModel != null) {
currentModel.setText(parser.getText());
}
} else if (token == XmlPullParser.END_TAG) {
// FIXME: Handle when "model" is a child of "model"
if ("model".equals(parser.getName())) {
currentModel = null;
}
}
}
return models;
}
}
With this in place, you can put the "parsing" of the attributes inside the Model
class, much like View
does it:
public class Model {
private String mName;
private String mType;
private int mStatAttack;
private String mText;
public Model(Context context, AttributeSet attrs) {
for (int i = 0; i < attrs.getAttributeCount(); i++) {
String attr = attrs.getAttributeName(i);
if ("name".equals(attr)) {
mName = attrs.getAttributeValue(i);
} else if ("type".equals(attr)) {
// This will load the value of the string resource you
// referenced in your XML
int stringResource = attrs.getAttributeResourceValue(i, 0);
mType = context.getString(stringResource);
} else if ("stat_attack".equals(attr)) {
mStatAttack = attrs.getAttributeIntValue(i, -1);
} else {
// TODO: Parse more attributes
}
}
}
public void setText(String text) {
mText = text;
}
@Override
public String toString() {
return "model name=" + mName + " type=" + mType + " stat_attack=" + mStatAttack + " text=" + mText;
}
}
Above I have referenced attributes through the string representation. If you want to go further, you can define application specific attribute resources and use those instead, but that will complicate things quite a bit (see Declaring a custom android UI element using XML). Anyway, with all resources setup and this in a dummy activity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
for (Model m : CustomInflator.inflate(this, R.xml.models)) {
Log.i("Example", "Parsed: " + m.toString());
}
} catch (Exception e) {
Log.e("Example", "Got " + e);
}
}
you'll get this output:
I/Example ( 1567): Parsed: model name=tall_model type=Example3 stat_attack=5 text=Tall Gunner
I/Example ( 1567): Parsed: model name=short_model type=Example3 stat_attack=3 text=Short Gunner
I/Example ( 1567): Parsed: model name=big_tank type=Example2 stat_attack=7 text=Big Tank
Note that you can't have @resource/scout
in your XML file since resource
is not a valid resource type, but @string/foo
works fine. You should also be able to use for example @drawable/foo
with some trivial modifications to the code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With