There's precious little documentation about the declare-styleable
tag by which we can declare custom styles for components. I did find this list of valid values for the format
attribute of the attr
tag. While that's nice as far as it goes, it doesn't explain how to use some of those values. Browsing attr.xml (the Android source for the standard attributes), I discovered that you can do things like:
<!-- The most prominent text color. -->
<attr name="textColorPrimary" format="reference|color" />
The format
attribute can evidently be set to a combination of values. Presumably the format
attribute helps the parser interpret an actual style value. Then I discovered this in 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>
Both of these seem to declare a set of allowed values for the indicated style.
So I have two questions:
- What's the difference between a style attribute that can take on one of a set of
enum
values and one that can take on a set of flag
values?
- Does anyone know of any better documentation for how
declare-styleable
works (other than reverse engineering the Android source code)?
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>