如何爲自定義屬性指定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日,畢