pros:
- auto-generated id
- styleable attribute
- access other resource reference in xml, compile error if the referred resource miss
- compile-time syntax check, compile error if you set wrong data type
.............................................................
- declare DataItem class for deserialize
DataListItem.java
class DataListItem {
@Override
public String toString() {
return "DataListItem{" +
"id=" + id +
", name='" + name + '\'' +
", drawable=" + drawable +
", flags=" + flags +
", enums=" + enums +
'}';
}
public DataListItem(@IdRes int id, String name, Drawable drawable, int flags, int enums) {
this.id = id;
this.name = name;
this.drawable = drawable;
this.flags = flags;
this.enums = enums;
}
public int id;
public String name;
public Drawable drawable;
public int flags;
public int enums;
}
- declare styleable attributes
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:app="http://schemas.android.com/apk/res-auto">
<declare-styleable name="DataTestItem">
<attr name="android:id" format="reference"/>
<attr name="android:drawable" format="reference"/>
<attr name="android:name" format="string"/>
<attr name="name2" format="flags">
<flag name="flag1" value="1"/>
<flag name="flag2" value="2"/>
<flag name="flag3" value="4"/>
</attr>
<attr name="name3" format="enum">
<enum name="enum1" value="1"/>
<enum name="enum2" value="2"/>
<enum name="enum3" value="3"/>
</attr>
</declare-styleable>
</resources>
- create xml resource for DataItem list & map
test_list.xml
<?xml version="1.0" encoding="utf-8"?>
<list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:name="@string/app_name"
android:id="@+id/data1"
android:drawable="@drawable/ic_launcher_background"
app:name2="flag1|flag2"
app:name3="enum1"/>
<item
android:name="@string/app_name"
android:id="@+id/data2"
android:drawable="@drawable/ic_launcher_background"
app:name2="flag2"
app:name3="enum2"/>
<item
android:name="@string/app_name"
android:id="@+id/data3"
android:drawable="@drawable/ic_launcher_background"
app:name2="flag3|flag2"
app:name3="enum3"/>
</list>
test_map.xml
<?xml version="1.0" encoding="utf-8"?>
<map xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/data1"
android:drawable="@drawable/ic_launcher_background" />
<item
android:id="@+id/data2"
android:drawable="@drawable/ic_launcher_background" />
<item
android:id="@+id/data3"
android:drawable="@drawable/ic_launcher_background" />
</map>
test_set.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:name="@string/app_name" />
<item android:name="@string/app_name2" />
<item android:name="@string/app_name" />
<item android:name="@string/app_name" />
</set>
- load DataItem list & map from xml resource
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
List<DataListItem> dataListItems = loadListFromXml(this,
R.xml.test_list,
R.styleable.DataTestItem,
ArrayList::new,
(s) -> new DataListItem(
s.getResourceId(R.styleable.DataTestItem_android_id, 0),
s.getString(R.styleable.DataTestItem_android_name),
s.getDrawable(R.styleable.DataTestItem_android_drawable),
s.getInteger(R.styleable.DataTestItem_name2, 0),
s.getInteger(R.styleable.DataTestItem_name3, 0)));
Map<Integer, Drawable> dataMapItem = loadMapFromXML(this,
R.xml.test_map,
R.styleable.DataTestItem,
HashMap::new,
(s) -> s.getResourceId(R.styleable.DataTestItem_android_id, 0),
(s) -> s.getDrawable(R.styleable.DataTestItem_android_drawable));
Set<String> dataSetItems = loadSetFromXml(this,
R.xml.test_set,
R.styleable.DataTestItem,
HashSet::new,
(s) -> s.getString(R.styleable.DataTestItem_android_name));
Log.d(TAG, "loadListFromXml return " + dataListItems + ", loadMapFromXML return " + dataMapItem + ", loadSetFromXml return " + dataSetItems);
} catch (IOException | XmlPullParserException e) {
Log.w(TAG, "onCreate: fail to parse", e);
}
}
static <T> List<T> loadListFromXml(Context context,
@XmlRes int xml_resid,
@StyleableRes int[] attrs,
Supplier<List<T>> supplier,
Function<TypedArray, ? extends T> inflater) throws IOException, XmlPullParserException {
return collectFromXml(context,
xml_resid,
"list",
"item",
attrs,
supplier,
(s, ta) -> s.add(inflater.apply(ta)));
}
static <T> Set<T> loadSetFromXml(Context context,
@XmlRes int xml_resid,
@StyleableRes int[] attrs,
Supplier<Set<T>> supplier,
Function<TypedArray, ? extends T> inflater) throws IOException, XmlPullParserException {
return collectFromXml(context,
xml_resid,
"set",
"item",
attrs,
supplier,
(s, ta) -> s.add(inflater.apply(ta)));
}
static <K, V> Map<K, V> loadMapFromXML(Context context,
@XmlRes int xml_resid,
@StyleableRes int[] attrs,
Supplier<Map<K, V>> supplier,
Function<TypedArray, ? extends K> keyInflater,
Function<TypedArray, ? extends V> valueInflater) throws IOException, XmlPullParserException {
return collectFromXml(context,
xml_resid,
"map",
"item",
attrs,
supplier,
(s, ta) -> s.put(keyInflater.apply(ta), valueInflater.apply(ta)));
}
static <R> R collectFromXml(Context context,
@XmlRes int xml_resid,
@Nullable String rootNodeName,
@Nullable String itemNodeName,
@StyleableRes int[] attrs,
Supplier<R> supplier,
BiConsumer<R, TypedArray> accumulator) throws IOException, XmlPullParserException {
try (XmlResourceParser xml = context.getResources().getXml(xml_resid)) {
final R ret = supplier.get();
if (xml.next() == START_DOCUMENT) {
if (xml.nextTag() == START_TAG && ((rootNodeName == null) || rootNodeName.equals(xml.getName()))) {
while (xml.nextTag() == START_TAG && ((itemNodeName == null) || itemNodeName.equals(xml.getName()))) {
final TypedArray ta = context.obtainStyledAttributes(xml, attrs);
accumulator.accept(ret, ta);
ta.recycle();
if (xml.nextTag() != END_TAG) {
break;
}
}
}
}
return ret;
}
}
}