Reputation: 2934
I'm trying to find a way to get the raw unresolved value of an attribute. By that, I mean pretty much the exact text in the layout file, without having to parse the file directly.
Say there was a TextView like this:
<TextView
...
android:textColor="?colorAccent"
/>
I want to be able to pull out "?colorAccent" as a string, or the entry name of the value (like package:attr/colorAccent).
Is this possible without an XMLPullParser?
Upvotes: 2
Views: 634
Reputation: 2934
Going on what @Ben P. provided, I was able to come up with something that doesn't use reflection:
class Attributes(
private val context: Context,
private val attrs: AttributeSet
) {
fun getRawValue(@AttrRes attrId: Int): String {
val res = context.resources
val attrName = res.getResourceName(attrId)
val attrIndex = attrs.indexOfAttr(context) { it == attrName }
if (attrIndex == -1) return ""
val attrValue = attrs.getAttributeValue(attrIndex)
return when {
attrValue.startsWith('@') || attrValue.startsWith('?') -> {
val id = attrValue.substring(1)
.toInt()
res.getResourceName(id)
}
else -> attrValue
}
}
}
private fun AttributeSet.indexOfAttr(
context: Context,
matcher: (String) -> (Boolean)
): Int {
for (i in 0 until attributeCount) {
val nameResource = getAttributeNameResource(i)
val literalName = if (nameResource != 0) context.resources.getResourceName(nameResource) else ""
if (matcher(literalName)) {
return i
}
}
return -1
}
Here's a usage example:
class MyTextView(
context: Context,
attrs: AttributeSet? = null
) : AppCompatTextView() {
init {
if (attrs != null) {
val attributes = Attributes(context, attrs)
val rawTextColor = attributes.getRawValue(android.R.attr.textColor)
setText(rawTextColor)
}
}
}
The text view's text would be set to the "raw value" of Android's text color attribute which is provided in your layout XML.
If your text view's text was set to AndroidX's ?colorAccent
, the above code would populate the text view with the text [your-package]:attr/colorAccent
. It would do the same for a resource, returning the whole entry name including the package. If the attribute was set to literal text, it would just return that.
Upvotes: 0
Reputation: 54214
I'm using a view tag that looks like this for these examples:
<EditText
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="this is the hint"
android:text="@string/dummy_content"
android:textColor="?colorAccent" />
What's notable about this is that android:hint
is plain text, android:text
is a resource attribute, and android:textColor
is a style attribute.
For all three, I start with AttributeSet.getAttributeValue()
. For plain-text attributes, this will give you the actual value (e.g. for android:hint
this returns "this is the hint"
).
Resource attributes instead return a string that starts with @
and is then a number (e.g. for android:text
this returns "@2131689506"
). You can then parse the numeric portion of this string and use Resources.getResourceName()
to get the resolved name ("com.example.stackoverflow:string/dummy_content"
).
Style attributes return a string that starts with ?
and is then a number (e.g. for android:textColor
this returns "?2130903135"
). However, I do not know of any way to convert this number into a textual representation with supported APIs. Hopefully, though, this is enough to help someone else on the way to the full answer.
If you're willing to go off the rails, though, you can use reflection to find the text value of the style attribute. Because the string starts with ?
, you know it is either in R.attr
or android.R.attr
. You can scan these for a matching field with code like this:
private static String scan(Class<?> classToSearch, int target) {
for (Field field : classToSearch.getDeclaredFields()) {
try {
int fieldValue = (int) field.get(null);
if (fieldValue == target) {
return field.getName();
}
} catch (IllegalAccessException e) {
// TODO
}
}
return null;
}
int id = Integer.parseInt(attributeValue.substring(1));
String attrName = scan(R.attr.class, id);
String androidAttrName = scan(android.R.attr.class, id);
For me, this will output
colorAccent null
If the value of android:textColor
were ?android:colorAccent
instead of just ?colorAccent
, the output would instead be:
null colorAccent
Upvotes: 1