在 Android 中声明可设置样式的属性

有关 declare-styleable标记的文档非常少,我们可以通过这些文档声明组件的自定义样式。我确实找到了 attr标记的 format属性的有效值的 这份名单。虽然就目前而言这很好,但它并没有解释如何使用其中的一些值。通过浏览 Xml(用于标准属性的 Android 源代码) ,我发现可以这样做:

<!-- The most prominent text color.  -->
<attr name="textColorPrimary" format="reference|color" />

显然,可以将 format属性设置为值的组合。据推测,format属性可以帮助解析器解释实际的样式值。然后我在 attr.xml 中发现了这一点:

<!-- Default text typeface. -->
<attr name="typeface">
<enum name="normal" value="0" />
<enum name="sans" value="1" />
<enum name="serif" value="2" />
<enum name="monospace" value="3" />
</attr>


<!-- Default text typeface style. -->
<attr name="textStyle">
<flag name="normal" value="0" />
<flag name="bold" value="1" />
<flag name="italic" value="2" />
</attr>

这两者似乎都为指定的样式声明了一组允许的值。

我有两个问题:

  1. 可以接受一组 enum值中的一个的 style 属性和可以接受一组 flag值中的一个的 style 属性之间有什么区别?
  2. 有没有人知道关于 declare-styleable如何工作的更好的文档(除了逆向工程的 Android 源代码) ?
89693 次浏览

There's this question here: Defining custom attrs with some info, but not much.

And this post . It has good info about flags and enums:

Custom XML Attribute Flags

Flags are special attribute types in that they are allowed only a very small subset of values, namely those that are defined underneath the attribute tag. Flags are specified by a “name” attribute and a “value” attribute. The names are required to be unique within that attribute type but the values need not be. This is the reason that during the evolution of the Android platform we had “fill_parent” and “match_parent” both mapping to the same behavior. Their values were identical.

The name attribute maps to the name used in the value place within the layout XML and does not require a namespace prefix. Hence, for the “tilingMode” above I chose “center” as the attribute value. I could have just as easily chosen “stretched” or “repeating” but nothing else. Not even substituting in the actual values would have been allowed.

The value attribute must be an integer. The choice of hexadecimal or standard numeral representation is up to you. There’s a few places within the Android code where both are used and the Android compiler is happy to accept either.

Custom XML Attribute Enums

Enums are used in an almost identical manner as flags with one provision, they may be used interchangeably with integers. Under the hood Enums and Integers are mapped to the same data type, namely, an Integer. When appearing in the attribute definition with Integers, Enums serve to prevent “magic numbers” which are always bad. This is why you can have an “android:layout_width” with either a dimension, integer, or named string “fill_parent.”

To put this into context, let’s suppose that I create a custom attribute called “layout_scroll_height” which accepts either an integer or a string “scroll_to_top.” To do so I’d add an “integer” format attribute and follow that with the enum:

<attr name="layout_scroll_height" format="integer">
<enum name="scroll_to_top" value="-1"/>
</attr>

The one stipulation when using Enums in this manner is that a developer using your custom View could purposefully place the value “-1″ into the layout parameters. This would trigger the special case logic of “scroll_to_top.” Such unexpected (or expected) behavior could quickly relegate your library to the “legacy code” pile if the Enum values were chosen poorly.


As I see it, the real values you can add in reality to an attribute is limited by what you can obtain from it. Check the AttributeSet class reference here for more hints.

You can obtain:

  • booleans (getAttributeBooleanValue),
  • floats (getAttributeFloatValue),
  • ints (getAttributeIntValue),
  • ints (as getAttributeUnsignedIntValue),
  • and strings (getAttributeValue)

@Aleadam 's answer is very helpful, but imho it omits one major difference between enum and flag. The former is intented for us to pick one, and only one value when we assign the corresponding attribute for some View. The latter's values can be combined, however, using the bitwise OR operator.

An example, in res/values/attr.xml

<!-- declare myenum attribute -->
<attr name="myenum">
<enum name="zero" value="0" />
<enum name="one" value="1" />
<enum name="two" value="2" />
<enum name="three" value="3" />
</attr>


<!-- declare myflags attribute -->
<attr name="myflags">
<flag name="one" value="1" />
<flag name="two" value="2" />
<flag name="four" value="4" />
<flag name="eight" value="8" />
</attr>


<!-- declare our custom widget to be styleable by these attributes -->
<declare-styleable name="com.example.MyWidget">
<attr name="myenum" />
<attr name="myflags" />
</declare-styleable>

In res/layout/mylayout.xml we can now do

<com.example.MyWidget
myenum="two"
myflags="one|two"
... />

So an enum selects one of its possible values, while flags can be combined. The numerical values should reflect this difference, typically you'll want the sequence to go 0,1,2,3,... for enums (to be used as array indices, say) and flags to go 1,2,4,8,... so they can be independently added or removed, using bitwise OR | to combine flags.

We could explicitly define "meta flags" with values that are not a power of 2, and thus introduce a kind of shorthand for common combinations. For instance, if we had included this in our myflags declaration

<flag name="three" value="3" />

then we could have written myflags="three" in stead of myflags="one|two", for completely identical results as 3 == 1|2.

Personally, I like to always include

<flag name="none" value="0" /> <!-- or "normal, "regular", and so on -->
<flag name="all" value="15" /> <!-- 15 == 1|2|4|8 -->

which will allow me to unset or set all flags at once.

More subtly, it might be the case that one flag is implied by another. So, in our example, suppose that the eight flag being set should force the four flag to be set (if it wasn't already). We could then re-define eight to pre-include, as it were, the four flag,

<flag name="eight" value="12" /> <!-- 12 == 8|4 -->

Finally, if you are declaring the attributes in a library project but want to apply them in layouts of another project (dependent on the lib), you'll need to use a namespace prefix which you must bind in the XML root element. E.g.,

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:auto="http://schemas.android.com/apk/res-auto"
... >


<com.example.MyWidget
auto:myenum="two"
auto:myflags="one|two"
... />


</RelativeLayout>