如何为自定义属性指定format类型呢? 确实,在自定义组件时往往要为其提供一些属性,然后在布局文件或代码里根据自定义组件的属性格式类型设置初始值。起初,寄希望于在官方文档中寻找关于format支持哪些类型的答案。不过很可惜,官方文档并没有关于此方面的详细说明。而SDK自带的示例代码也未能揭示全部的真相。比如,在示例代码ApiDemos内的com.example.android.apis.view.LabelView.java文件内定义了一个自定义的视图,并在res/values/attrs.xml内为其定义了几个属性,内容如下:
<declare-styleable name="LabelView">
<attr name="text" format="string" />
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" />
</declare-styleable>
此外,我们还会发现该文件内其他的自定义属性,比如:
<declare-styleable name="DraggableDot">
<attr name="radius" format="dimension" />
<attr name="legend" format="string" />
<attr name="anr">
<enum name="none" value="0" />
<enum name="thumbnail" value="1" />
<enum name="drop" value="2" />
</attr>
</declare-styleable>
这个看上去比前一个要稍许复杂些,但是,还有比这更复杂的自定义属性吗?比如,在上面的两断代码里,我们发现format可以使用dimension,string,color字符串对其赋值,它们代表生命含义呢?除此之外,还有其他可以赋给format的字符串吗?还有,attr属性有时候并没有format值,而是包含了一些enum子属性,但它又代表什么呢?attr还有其他的子属性吗?
对于这样的问题,我觉得目前还没有一个实例代码可以让我们彻底死心而不再追问下去。因此,只能看看android源码吧。很幸运,通过源码工程内的文件名可以很容易找到关于处理android应用资源的代码文件,它就是ResourceTable.h/cpp,位于frameworks/base/tools/aapt目录下。我们只关注其中用来编译xml资源的代码段,在此段代码里我们会发现程序是如何处理attr的各种类型。代码如下:
static status_t compileAttribute(const sp<AaptFile>& in,
ResXMLTree& block,
const String16& myPackage,
ResourceTable* outTable,
String16* outIdent = NULL,
bool inStyleable = false)
{
PendingAttribute attr(myPackage, in, block, inStyleable);
const String16 attr16("attr");
const String16 id16("id");
// Attribute type constants.
const String16 enum16("enum");
const String16 flag16("flag");
ResXMLTree::event_code_t code;
size_t len;
status_t err;
ssize_t identIdx = block.indexOfAttribute(NULL, "name");
if (identIdx >= 0) {
//maybe <declare-styleable>
// <attr name="attrName" format="fmt" />
// so , attr.ident = "attrName";
//++
attr.ident = String16(block.getAttributeStringValue(identIdx, &len));
//--
if (outIdent) {
*outIdent = attr.ident;
}
} else {
attr.sourcePos.error("A 'name' attribute is required for <attr>\n");
attr.hasErrors = true;
}
attr.comment = String16(
block.getComment(&len) ? block.getComment(&len) : nulStr);
//对format属性开始解析:
ssize_t typeIdx = block.indexOfAttribute(NULL, "format");
if (typeIdx >= 0) {
String16 typeStr = String16(block.getAttributeStringValue(typeIdx, &len)); //获取属性attr的format类型字符串形式:
attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags);//gFormatFlags为全局类型数组,组内每项包括了类型的字符串形式,长度,值及描述等信息,
//parse_flags用来将获取的类型字符串与组内包含的类型信息进行比对。
if (attr.type == 0) {
attr.sourcePos.error("Tag <attr> 'format' attribute value \"%s\" not valid\n",
String8(typeStr).string());
attr.hasErrors = true;
}
attr.createIfNeeded(outTable);
} else if (!inStyleable) {
// Attribute definitions outside of styleables always define the
// attribute as a generic value.
attr.createIfNeeded(outTable);
}
//printf("Attribute %s: type=0x%08x\n", String8(attr.ident).string(), attr.type);
ssize_t minIdx = block.indexOfAttribute(NULL, "min");
if (minIdx >= 0) {
String16 val = String16(block.getAttributeStringValue(minIdx, &len));
if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
attr.sourcePos.error("Tag <attr> 'min' attribute must be a number, not \"%s\"\n",
String8(val).string());
attr.hasErrors = true;
}
attr.createIfNeeded(outTable);
if (!attr.hasErrors) {
err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
String16(""), String16("^min"), String16(val), NULL, NULL);
if (err != NO_ERROR) {
attr.hasErrors = true;
}
}
}
ssize_t maxIdx = block.indexOfAttribute(NULL, "max");
if (maxIdx >= 0) {
String16 val = String16(block.getAttributeStringValue(maxIdx, &len));
if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
attr.sourcePos.error("Tag <attr> 'max' attribute must be a number, not \"%s\"\n",
String8(val).string());
attr.hasErrors = true;
}
attr.createIfNeeded(outTable);
if (!attr.hasErrors) {
err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
String16(""), String16("^max"), String16(val), NULL, NULL);
attr.hasErrors = true;
}
}
if ((minIdx >= 0 || maxIdx >= 0) && (attr.type&ResTable_map::TYPE_INTEGER) == 0) {
attr.sourcePos.error("Tag <attr> must have format=integer attribute if using max or min\n");
attr.hasErrors = true;
}
ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization");
if (l10nIdx >= 0) {
const uint16_t* str = block.getAttributeStringValue(l10nIdx, &len);
bool error;
uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error);
if (error) {
attr.sourcePos.error("Tag <attr> 'localization' attribute value \"%s\" not valid\n",
String8(str).string());
attr.hasErrors = true;
}
attr.createIfNeeded(outTable);
if (!attr.hasErrors) {
char buf[11];
sprintf(buf, "%d", l10n_required);
err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
String16(""), String16("^l10n"), String16(buf), NULL, NULL);
if (err != NO_ERROR) {
attr.hasErrors = true;
}
}
}
String16 enumOrFlagsComment;
while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::START_TAG) {
uint32_t localType = 0;
if (strcmp16(block.getElementName(&len), enum16.string()) == 0) {
localType = ResTable_map::TYPE_ENUM;
} else if (strcmp16(block.getElementName(&len), flag16.string()) == 0) {
localType = ResTable_map::TYPE_FLAGS;
} else {
SourcePos(in->getPrintableSource(), block.getLineNumber())
.error("Tag <%s> can not appear inside <attr>, only <enum> or <flag>\n",
String8(block.getElementName(&len)).string());
return UNKNOWN_ERROR;
}
attr.createIfNeeded(outTable);
if (attr.type == ResTable_map::TYPE_ANY) {
// No type was explicitly stated, so supplying enum tags
// implicitly creates an enum or flag.
attr.type = 0;
}
if ((attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) == 0) {
// Wasn't originally specified as an enum, so update its type.
attr.type |= localType;
if (!attr.hasErrors) {
char numberStr[16];
sprintf(numberStr, "%d", attr.type);
err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
myPackage, attr16, attr.ident, String16(""),
String16("^type"), String16(numberStr), NULL, NULL, true);
if (err != NO_ERROR) {
attr.hasErrors = true;
}
}
} else if ((uint32_t)(attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) != localType) {
if (localType == ResTable_map::TYPE_ENUM) {
SourcePos(in->getPrintableSource(), block.getLineNumber())
.error("<enum> attribute can not be used inside a flags format\n");
attr.hasErrors = true;
} else {
SourcePos(in->getPrintableSource(), block.getLineNumber())
.error("<flag> attribute can not be used inside a enum format\n");
attr.hasErrors = true;
}
}
String16 itemIdent;
ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
if (itemIdentIdx >= 0) {
itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
} else {
SourcePos(in->getPrintableSource(), block.getLineNumber())
.error("A 'name' attribute is required for <enum> or <flag>\n");
attr.hasErrors = true;
}
String16 value;
ssize_t valueIdx = block.indexOfAttribute(NULL, "value");
if (valueIdx >= 0) {
value = String16(block.getAttributeStringValue(valueIdx, &len));
} else {
SourcePos(in->getPrintableSource(), block.getLineNumber())
.error("A 'value' attribute is required for <enum> or <flag>\n");
attr.hasErrors = true;
}
if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) {
SourcePos(in->getPrintableSource(), block.getLineNumber())
.error("Tag <enum> or <flag> 'value' attribute must be a number,"
" not \"%s\"\n",
String8(value).string());
attr.hasErrors = true;
}
// Make sure an id is defined for this enum/flag identifier...
if (!attr.hasErrors && !outTable->hasBagOrEntry(itemIdent, &id16, &myPackage)) {
err = outTable->startBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
myPackage, id16, itemIdent, String16(), NULL);
if (err != NO_ERROR) {
attr.hasErrors = true;
}
}
if (!attr.hasErrors) {
if (enumOrFlagsComment.size() == 0) {
enumOrFlagsComment.append(mayOrMust(attr.type,
ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS));
enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM)
? String16(" be one of the following constant values.")
: String16(" be one or more (separated by '|') of the following constant values."));
enumOrFlagsComment.append(String16("</p>\n<table>\n"
"<colgroup align=\"left\" />\n"
"<colgroup align=\"left\" />\n"
"<colgroup align=\"left\" />\n"
"<tr><th>Constant</th><th>Value</th><th>Description</th></tr>"));
}
enumOrFlagsComment.append(String16("\n<tr><td><code>"));
enumOrFlagsComment.append(itemIdent);
enumOrFlagsComment.append(String16("</code></td><td>"));
enumOrFlagsComment.append(value);
enumOrFlagsComment.append(String16("</td><td>"));
if (block.getComment(&len)) {
enumOrFlagsComment.append(String16(block.getComment(&len)));
}
enumOrFlagsComment.append(String16("</td></tr>"));
err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
myPackage,
attr16, attr.ident, String16(""),
itemIdent, value, NULL, NULL, false, true);
if (err != NO_ERROR) {
attr.hasErrors = true;
}
}
} else if (code == ResXMLTree::END_TAG) {
if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
break;
}
if ((attr.type&ResTable_map::TYPE_ENUM) != 0) {
if (strcmp16(block.getElementName(&len), enum16.string()) != 0) {
SourcePos(in->getPrintableSource(), block.getLineNumber())
.error("Found tag </%s> where </enum> is expected\n",
String8(block.getElementName(&len)).string());
return UNKNOWN_ERROR;
}
} else {
if (strcmp16(block.getElementName(&len), flag16.string()) != 0) {
SourcePos(in->getPrintableSource(), block.getLineNumber())
.error("Found tag </%s> where </flag> is expected\n",
String8(block.getElementName(&len)).string());
return UNKNOWN_ERROR;
}
}
}
}
if (!attr.hasErrors && attr.added) {
appendTypeInfo(outTable, myPackage, attr16, attr.ident, attr.type, gFormatFlags);
}
if (!attr.hasErrors && enumOrFlagsComment.size() > 0) {
enumOrFlagsComment.append(String16("\n</table>"));
outTable->appendTypeComment(myPackage, attr16, attr.ident, enumOrFlagsComment);
}
return NO_ERROR;
}
gFormatFlags的定义如下:
static const flag_entry gFormatFlags[] = {
{ referenceArray, sizeof(referenceArray)/2, ResTable_map::TYPE_REFERENCE,
"a reference to another resource, in the form \"<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>\"\n"
"or to a theme attribute in the form \"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\"."},
{ stringArray, sizeof(stringArray)/2, ResTable_map::TYPE_STRING,
"a string value, using '\\\\;' to escape characters such as '\\\\n' or '\\\\uxxxx' for a unicode character." },
{ integerArray, sizeof(integerArray)/2, ResTable_map::TYPE_INTEGER,
"an integer value, such as \"<code>100</code>\"." },
{ booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN,
"a boolean value, either \"<code>true</code>\" or \"<code>false</code>\"." },
{ colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR,
"a color value, in the form of \"<code>#<i>rgb</i></code>\", \"<code>#<i>argb</i></code>\",\n"
"\"<code>#<i>rrggbb</i></code>\", or \"<code>#<i>aarrggbb</i></code>\"." },
{ floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT,
"a floating point value, such as \"<code>1.2</code>\"."},
{ dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION,
"a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n"
"Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n"
"in (inches), mm (millimeters)." },
{ fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION,
"a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n"
"The % suffix always means a percentage of the base size; the optional %p suffix provides a size relative to\n"
"some parent container." },
{ enumArray, sizeof(enumArray)/2, ResTable_map::TYPE_ENUM, NULL },
{ flagsArray, sizeof(flagsArray)/2, ResTable_map::TYPE_FLAGS, NULL },
{ NULL, 0, 0, NULL }
};
当面看到ResTable_map常量成员时,我们似乎发现了些什么,但真正的答案在于那些以"Array"结尾的变量里,它们都是字符指针,定义如下:
static const char16_t referenceArray[] =
{ 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e' }; //"reference"
static const char16_t stringArray[] =
{ 's', 't', 'r', 'i', 'n', 'g' }; //"string"
static const char16_t integerArray[] =
{ 'i', 'n', 't', 'e', 'g', 'e', 'r' }; //"integer"
static const char16_t booleanArray[] =
{ 'b', 'o', 'o', 'l', 'e', 'a', 'n' }; //"boolean"
static const char16_t colorArray[] =
{ 'c', 'o', 'l', 'o', 'r' }; //"color"
static const char16_t floatArray[] =
{ 'f', 'l', 'o', 'a', 't' }; //"float"
static const char16_t dimensionArray[] =
{ 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n' }; //"dimension"
static const char16_t fractionArray[] =
{ 'f', 'r', 'a', 'c', 't', 'i', 'o', 'n' }; //"fraction"
static const char16_t enumArray[] =
{ 'e', 'n', 'u', 'm' }; //"enum"
static const char16_t flagsArray[] =
{ 'f', 'l', 'a', 'g', 's' }; //"flags"
而attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags)方法内部就是使用上述的字符指针内的内容和typeStr进行比对的。
parse_flags方法的定义如下:
static uint32_t parse_flags(const char16_t* str, size_t len,
const flag_entry* flags, bool* outError = NULL)
{
while (len > 0 && isspace(*str)) {
str++;
len--;
}
while (len > 0 && isspace(str[len-1])) {
len--;
}
const char16_t* const end = str + len;
uint32_t value = 0;
while (str < end) {
const char16_t* div = str;
//说明format的类型可以是多个类型的组合!!
while (div < end && *div != '|') {
div++;
}
const flag_entry* cur = flags;
while (cur->name) {
if (strzcmp16(cur->name, cur->nameLen, str, div-str) == 0) {
value |= cur->value;
break;
}
cur++;
}
if (!cur->name) {
if (outError) *outError = true;
return 0;
}
str = div < end ? div+1 : div;
}
if (outError) *outError = false;
return value;
}
flag_entry的类型定义如下:
struct flag_entry
{
const char16_t* name;
size_t nameLen;
uint32_t value;
const char* description;
};
ResTable_map定义在resourceTypes.h文件内,定义如下:
struct ResTable_map
{
// The resource identifier defining this mapping's name. For attribute
// resources, 'name' can be one of the following special resource types
// to supply meta-data about the attribute; for all other resource types
// it must be an attribute resource.
ResTable_ref name;
// Special values for 'name' when defining attribute resources.
enum {
// This entry holds the attribute's type code.
ATTR_TYPE = Res_MAKEINTERNAL(0),
// For integral attributes, this is the minimum value it can hold.
ATTR_MIN = Res_MAKEINTERNAL(1),
// For integral attributes, this is the maximum value it can hold.
ATTR_MAX = Res_MAKEINTERNAL(2),
// Localization of this resource is can be encouraged or required with
// an aapt flag if this is set
ATTR_L10N = Res_MAKEINTERNAL(3),
// for plural support, see android.content.res.PluralRules#attrForQuantity(int)
ATTR_OTHER = Res_MAKEINTERNAL(4),
ATTR_ZERO = Res_MAKEINTERNAL(5),
ATTR_ONE = Res_MAKEINTERNAL(6),
ATTR_TWO = Res_MAKEINTERNAL(7),
ATTR_FEW = Res_MAKEINTERNAL(8),
ATTR_MANY = Res_MAKEINTERNAL(9)
};
// Bit mask of allowed types, for use with ATTR_TYPE.
enum {
// No type has been defined for this attribute, use generic
// type handling. The low 16 bits are for types that can be
// handled generically; the upper 16 require additional information
// in the bag so can not be handled generically for TYPE_ANY.
TYPE_ANY = 0x0000FFFF,
// Attribute holds a references to another resource.
TYPE_REFERENCE = 1<<0,
// Attribute holds a generic string.
TYPE_STRING = 1<<1,
// Attribute holds an integer value. ATTR_MIN and ATTR_MIN can
// optionally specify a constrained range of possible integer values.
TYPE_INTEGER = 1<<2,
// Attribute holds a boolean integer.
TYPE_BOOLEAN = 1<<3,
// Attribute holds a color value.
TYPE_COLOR = 1<<4,
// Attribute holds a floating point value.
TYPE_FLOAT = 1<<5,
// Attribute holds a dimension value, such as "20px".
TYPE_DIMENSION = 1<<6,
// Attribute holds a fraction value, such as "20%".
TYPE_FRACTION = 1<<7,
// Attribute holds an enumeration. The enumeration values are
// supplied as additional entries in the map.
TYPE_ENUM = 1<<16,
// Attribute holds a bitmaks of flags. The flag bit values are
// supplied as additional entries in the map.
TYPE_FLAGS = 1<<17
};
// Enum of localization modes, for use with ATTR_L10N.
enum {
L10N_NOT_REQUIRED = 0,
L10N_SUGGESTED = 1
};
// This mapping's value.
Res_value value;
};
至此,我们终于知道format究竟支持多少种类型了。现在我们来定义一个较为全面的属性文件:
<declare-styleable name="mayAttrs">
<attr name="myColor" format="color"/>
<attr name="myInter" format="integer"/>
<attr name="myRef" format="reference"/>
<attr name="myBool" format="boolean" />
<attr name="myStr" format="string"/>
<attr name="myFloat" format="float"/>
<attr name="myFrag" format="fraction"/>
<attr name="myDimes" format="dimension"/>
<attr name="myEnum" format="enum">
<enum name="none" value="0" />
<enum name="thumbnail" value="1" />
<enum name="drop" value="2" />
</attr>
<attr name="myFlag" format="flags">
<flag name="none" value="1" />
<flag name="thumbnail" value="2" />
<flag name="drop" value="4" />
</attr>
<attr name="myEnum1">
<enum name="none" value="0" />
<enum name="thumbnail" value="1" />
<enum name="drop" value="2" />
</attr>
<attr name="myFlag1" >
<flag name="none" value="1" />
<flag name="thumbnail" value="2" />
<flag name="drop" value="4" />
</attr>
<!-- 可以组合使用-->
<attr name="myColorRef" format="color|reference"/>
</declare-styleable>
最后,这里还有一个链接,http://www.androidadb.com/source/pdn-slatedroid-read-only/eclair/sdk/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParser.java.html,它显示了ADT解析属性XML文件的源代码。
2012年6月7日,毕