Reputation: 1068
I am trying to implement a resource data structure that includes an array of arrays, specifically strings. The issue I run into is how to get the sub-array objects and their specific values. Here is what my resource file looks like....
<resources>
<array name="array0">
<item>
<string-array name="array01">
<item name="id">1</item>
<item name="title">item one</item>
</string-array>
</item>
<item>
<string-array name="array02">
<item name="id">2</item>
<item name="title">item two</item>
</string-array>
</item>
<item>
<string-array name="array03">
<item name="id">3</item>
<item name="title">item three</item>
</string-array>
</item>
</array>
</resources>
Then, in my Java code I retrieve the array and try to access the sub elements like so...
TypedArray typedArray = getResources().obtainTypedArray(R.array.array0);
TypedValue typedValue = null;
typedArray.getValue(0, typedValue);
At this point the typedArray object should represent the string-array "array01", however, I don't see how to retrieve the "id" and "title" string elements. Any help would be appreciated, thanks in advance.
Upvotes: 47
Views: 37340
Reputation: 1352
pros:
.............................................................
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;
}
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>
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>
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;
}
}
}
Upvotes: 0
Reputation: 1268
https://developer.android.com/guide/topics/resources/more-resources.html#Color
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="icons">
<item>@drawable/home</item>
<item>@drawable/settings</item>
<item>@drawable/logout</item>
</array>
<array name="colors">
<item>#FFFF0000</item>
<item>#FF00FF00</item>
<item>#FF0000FF</item>
</array>
</resources>
This application code retrieves each array and then obtains the first entry in each array:
Resources res = getResources();
TypedArray icons = res.obtainTypedArray(R.array.icons);
Drawable drawable = icons.getDrawable(0);
TypedArray colors = res.obtainTypedArray(R.array.colors);
int color = colors.getColor(0,0);
Upvotes: 2
Reputation: 21
according to @Ted Hopp answer, more elegant way:
<integer-array name="id_array">
<item>1</item>
<item>2</item>
<item>3</item>
</integer-array>
<string-array name="name_array">
<item>name 1</item>
<item>name 2</item>
<item>name 3</item>
</string-array>
<array name="array0">
<item>@array/id_array</item>
<item>@array/name_array</item>
</array>
make sure sub arrays row count is identical.
enjoy write code to access array cells!
android is still a kid while maintain the ugly "item" tag of the TypedArray "array0".
in my opinion, the most flexible should be:
<array name="array0">
<integer-array name="id_array">
<item>1</item>
<item>2</item>
<item>3</item>
</integer-array>
<string-array name="name_array">
<item>name 1</item>
<item>name 2</item>
<item>name 3</item>
</string-array>
</array>
but don't do that because that's not android way :)
Upvotes: 1
Reputation: 234795
You can almost do what you want. You have to declare each array separately and then an array of references. Something like this:
<string-array name="array01">
<item name="id">1</item>
<item name="title">item one</item>
</string-array>
<!-- etc. -->
<array name="array0">
<item>@array/array01</item>
<!-- etc. -->
</array>
Then in your code you do something like this:
Resources res = getResources();
TypedArray ta = res.obtainTypedArray(R.array.array0);
int n = ta.length();
String[][] array = new String[n][];
for (int i = 0; i < n; ++i) {
int id = ta.getResourceId(i, 0);
if (id > 0) {
array[i] = res.getStringArray(id);
} else {
// something wrong with the XML
}
}
ta.recycle(); // Important!
Upvotes: 124
Reputation: 1383
An Array isn't a name/value pair. You only can access the elements by number. The syntax in the xml is wrong. It should be that way:
<string-array name="string_array_name">
<item>text_string1</item>
<item>text_string2</item>
</string-array>
Upvotes: -2