如何从AttributeSet中获取属性的原始,未解析的值?

时间:2018-09-25 00:11:40

标签: android view attributes

我正在尝试找到一种方法来获取属性的原始未解析值。这样,我的意思是布局文件中的确切文本,而不必直接解析该文件。

说有一个像这样的TextView:

<TextView
    ...
    android:textColor="?colorAccent"
    />

我希望能够提取“?colorAccent” 作为字符串,或值的条目名称 (例如package:attr / colorAccent )。

如果没有XMLPullParser,这是否可能?

2 个答案:

答案 0 :(得分:1)

在这些示例中,我使用的视图标签如下:

<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" />

值得注意的是android:hint是纯文本,android:text是资源属性,而android:textColor是样式属性。

对于这三个,我都从AttributeSet.getAttributeValue()开始。对于纯文本属性,这将为您提供实际值(例如,对于android:hint,它返回"this is the hint")。

Resource属性返回一个以@开头,然后为数字的字符串(例如,对于android:text,它返回"@2131689506")。然后,您可以解析此字符串的数字部分,并使用Resources.getResourceName()获取解析的名称("com.example.stackoverflow:string/dummy_content")。

样式属性返回以?开头且为数字的字符串(例如,对于android:textColor,它返回"?2130903135")。但是,我不知道有任何方法可以将此数字转换为带有受支持的API的文本表示形式。不过,希望这足以帮助其他人获得完整答案。

使用反射

但是,如果您愿意摆脱困境,则可以使用反射来查找样式属性的文本值。因为字符串以?开头,所以您知道它在R.attrandroid.R.attr中。您可以使用以下代码扫描这些字段以查找匹配的字段:

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);

对我来说,这将输出

colorAccent
null

如果android:textColor的值为?android:colorAccent而不是?colorAccent,则输出为:

null
colorAccent

答案 1 :(得分:0)

在@Ben P.提供的内容上,我能够提出一些不使用反射的内容:

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
}

这是一个用法示例:

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)
     }
  }
}

文本视图的文本将设置为布局XML中提供的Android文本颜色属性的“原始值”。

如果您的文本视图的文本设置为AndroidX的?colorAccent,则上面的代码将使用文本[your-package]:attr/colorAccent填充文本视图。它将对资源执行相同的操作,返回包括包的整个条目名称。如果该属性设置为文字文本,则只会返回该文本。