preface
To recap: When customizing a View, you need to provide an external method to set the color according to different conditions. A View can be referenced in XML, so can we set color properties in XML based on different conditions? That’s flexible. Of course, Android is ready for us, so let’s take a look at how it resolves and uses it. Through this article, you will learn:
3, attR /style/theme connection and difference 4, custom attribute loading priority 5, custom attribute loading source analysis
The base of custom attributes is clear
attrs.xml
Note for convenience, the following “property declaration” means that the property is declared but not assigned; And “attribute definition” means that the attribute is used, that is, assigned.
Create a new attrs. XML file in the res->values directory that declares the attribute name and the data format it accepts:
<? The XML version = "1.0" encoding = "utf-8"? > <resources> <attr name="attr_str" format="string"></attr> <attr name="attr_bool" format="boolean"></attr> <attr name="attr_int" format="integer"></attr> <attr name="attr_ref" format="reference"></attr> </resources>Copy the code
Name indicates the attribute name, and format indicates the input format accepted by the attribute. Three attributes are declared, representing the string, Boolean, INTEGER, and Reference formats respectively. Reference points to other resources. Format Also has other formats such as:
Color — value dimension — size float — value fraction — percentage enum — enumeration flag — bit or operation mixed type — multiple formats combined
Enum and flag declarations (format may not be specified) are as follows:
<? The XML version = "1.0" encoding = "utf-8"? > <resources> <attr name="attr_enum"> <enum name="first" value="1"></enum> <enum name="second" value="2"></enum> <enum name="third" value="3"></enum> </attr> <attr name="attr_flag"> <flag name="east" value="0x1"></flag> <flag name="west" value="0x2"></flag> <flag name="south" value="0x3"></flag> <flag name="north" value="0x4"></flag> </attr> </resources>Copy the code
Custom attribute usage
Original mode of use
public class MyAttrView extends View { private final String TAG = MyAttrView.class.getSimpleName(); public MyAttrView(Context context) { super(context); } public MyAttrView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); for (int i = 0; i < attrs.getAttributeCount(); i++) { Log.d(TAG, "name:" + attrs.getAttributeName(i) + " value:" + attrs.getAttributeValue(i)); }}}Copy the code
Define View: MyAttrView and reference this View in the layout XML:
<com.fish.myapplication.attr.MyAttrView
app:attr_str="hello world str"
app:attr_bool="true"
app:attr_int="99"
app:attr_ref="@dimen/dp_100"
android:layout_width="100px"
android:layout_height="100px">
</com.fish.myapplication.attr.MyAttrView>
Copy the code
The four attributes declared in attrs. XML are referenced in the layout XML and parsed in MyAttrView. AttributeSet is an XML parsing utility class that helps us extract attribute names and attribute values from the layout XML. It is an interface implemented by Parser, a subclass of XmlBlock. Print the property name and value as follows:
The attr_ref attribute expects an integer size but returns the resource number. Layout_width /layout_height we don’t care about this property, we only care about custom properties. It would be nice if you could pass in the set of properties you care about and return their values.
Is there a way to solve both of these problems? The answer is TypedArray.
Advanced Usage (TypedArray)
Attrs.xml is still modified:
<resources> <declare-styleable name="MyStyleable"> <attr name="attr_str" format="string"></attr> <attr name="attr_bool" format="boolean"></attr> <attr name="attr_int" format="integer"></attr> <attr name="attr_ref" format="reference"></attr> </declare-styleable> </resources>Copy the code
The “register-styleable” tag has been added to MyStyleable compared to the attributes we declared initially, which means that several attribute declarations belong to the “same group”. Let’s see how to parse these properties.
public MyAttrView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); / / R.s tyleable MyStyleable refers to want to parse attributes TypedArray TypedArray = context. ObtainStyledAttributes (attrs. R.styleable.MyStyleable); Int count = typeDarray.getIndexCount (); for (int i = 0; i < count; i++) { int indexValue = typedArray.getIndex(i); Switch (indexValue) {case r.styleable. MyStyleable_attr_str: String strValue = typedArray.getString(indexValue); Log.d(TAG, "str value:" + strValue); break; case R.styleable.MyStyleable_attr_bool: boolean boolValue = typedArray.getBoolean(indexValue, false); Log.d(TAG, "bool value:" + boolValue); break; case R.styleable.MyStyleable_attr_int: int intValue = typedArray.getInt(indexValue, 0); Log.d(TAG, "int value:" + intValue); break; case R.styleable.MyStyleable_attr_ref: float refValue = typedArray.getDimension(indexValue, 0); Log.d(TAG, "float value:" + refValue); break; Return typedarray.recycle (); return typedarray.recycle (); }Copy the code
The running effect is as follows:
Key methods are as follows: the context. ObtainStyledAttributes (attrs, R.s tyleable MyStyleable)
public final TypedArray obtainStyledAttributes(
@Nullable AttributeSet set, @NonNull @StyleableRes int[] attrs) {
return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
}
Copy the code
There are two parameters:
AttributeSet set: set of attributes currently declared in XML int[] attrs: set of attribute names for which the value of the attribute is to be obtained
You can see that r.styleable.MyStyleable is actually an integer array. Like other resources in the RES directory, its index is generated in R. Java at compile time.
In r. Java, MyStyleable_attr_bool stands for array index. MyStyleable stands for array of attributes. Attr_bool stands for attributes
To sum up, TypedArray nicely solves the problem of getting properties “the raw way”.
Attr /style/theme Connection and difference
Origin and function of style
In res->values, find the styles. XML file (new if not):
<resources>
<style name="myStyle">
<item name="attr_str">str in myStyle</item>
<item name="attr_bool">true</item>
</style>
</resources>
Copy the code
You can see that style defines a batch of properties. The benefit of doing this is obvious: it makes it easier to reuse collections of properties. For example, our custom MyAttrView is used as a public control:
<com.fish.myapplication.attr.MyAttrView app:attr_str="hello world str" app:attr_bool="true" android:layout_width="100px" android:layout_height="100px"> </com.fish.myapplication.attr.MyAttrView>Copy the code
The attributes used are all the same, so you can extract these attributes as a style item and reference the style instead of repeating the attributes everywhere.
<com.fish.myapplication.attr.MyAttrView
style="@style/myStyle"
android:layout_width="100px"
android:layout_height="100px">
</com.fish.myapplication.attr.MyAttrView>
Copy the code
The origin and function of theme
In the res->values directory, find the themes. XML file (if not, create a new one)
<? The XML version = "1.0" encoding = "utf-8"? > <resources> <style name="myTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="attr_str">str in myTheme</item> <item name="attr_bool">true</item> </style> </resources>Copy the code
Parent indicates the theme of the parent class, and subclasses inherit the attributes of the parent class. How is theme used? Style is used to reuse property sets between views. Theme is used to reuse property sets for activities/applications. Therefore, we configure the theme for the Activity or Application.
Style is a collection of defined properties, using the style tag on View theme is a collection of defined properties, using the style tag on Application/Activity declare-styleable is a collection of declared properties, Use the declare-styleable label
Custom attribute loading priority
According to the above analysis, there are currently three ways to define attributes:
Define properties in the layout file. 2. Define properties in the style file
Look again at the obtainStyledAttributes(xx) method:
public final TypedArray obtainStyledAttributes(
@Nullable AttributeSet set, @NonNull @StyleableRes int[] attrs) {
return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
}
public TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
@NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes);
}
Copy the code
The second method has four parameters, the first two have been analyzed before, let’s look at the latter two:
@attrres int defStyleAttr If the parameter is AttrRes, the attribute type is @styleres int defStyleRes if the parameter is StyleRes, the attribute type is style
From the parameter name, it is obvious that this is the default property and the default style.
As you can see, the obtainStyledAttributes(xx) method is responsible for resolving attributes from five places:
1. Define properties in layout file 2. Define properties in style 3
The question is: if the same attribute is defined for all five sites, which attribute value should prevail? Before the truth is revealed, with the most basic method, one by one test to see the law. Use the “attr_str” attribute as an example:
# layout. The XML definition and use of the < com. Fish. Myapplication. Attr. MyAttrView app: attr_str = "STR in myLayout" android: layout_width = "100 px" android:layout_height="100px"> </com.fish.myapplication.attr.MyAttrView>Copy the code
2. Define attributes in style and use:
#styles. XML defines <style name="myStyle"> <item name="attr_str"> STR in myStyle</item> </style> #layout.xml to use style <com.fish.myapplication.attr.MyAttrView style="@style/myStyle" android:layout_width="100px" android:layout_height="100px"> </com.fish.myapplication.attr.MyAttrView>Copy the code
3. Use the default properties:
#themes. XML defines #attr_ref as an attribute of the reference type, Here to style < style name = "myTheme" parent "=" Theme. AppCompat. Light. DarkActionBar "> < item name="attr_ref">@style/myDefaultAttr</item> </style> #styles.xml <style name="myDefaultAttr"> <item name="attr_str">str In myDefaultAttr</item> </style> # myattrview.java parse passed r.attr. Attr_ref, Eventually find myDefaultAttr attr_str attribute in the context. ObtainStyledAttributes (attrs, R.s tyleable MyStyleable, state Richard armitage TTR event. Attr_ref, 0).Copy the code
4, use default style:
> <item name="attr_str"> STR in myDefaultStyle</item> </style> # myattrview.java parses the incoming r.style.mydefaultstyle, Eventually find myDefaultStyle attr_str attribute in the context. ObtainStyledAttributes (attrs, R.s tyleable. MyStyleable, 0, R.style.myDefaultStyle);Copy the code
5. Use the properties defined in theme:
# themes. In the XML definition < style name = "myTheme" parent "=" Theme. AppCompat. Light. DarkActionBar "> < item name =" attr_str "> STR in myTheme</item> </style> context.obtainStyledAttributes(attrs, R.styleable.MyStyleable, 0, 0);Copy the code
To distinguish where the attribute values come from, we print the corresponding keywords in different places. The above defines 1 to 5 different attributes, now add these attributes in reverse order from 5 to 1. Use TypedArray to resolve property values:
public MyAttrView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); / / R.s tyleable MyStyleable refers to want to parse attributes TypedArray TypedArray = context. ObtainStyledAttributes (attrs, null, 0, R.style.myDefaultStyle); Int count = typeDarray.getIndexCount (); for (int i = 0; i < count; i++) { int indexValue = typedArray.getIndex(i); Switch (indexValue) {case r.styleable. MyStyleable_attr_str: String strValue = typedArray.getString(indexValue); Log.d(TAG, "str value:" + strValue); break; } } typedArray.recycle(); }Copy the code
The five running results are as follows:
1. Define properties in layout file 2. Define properties in style 3
Custom attribute loading source code analysis
While the above tests illustrate how attributes are parsed and their priority, to better understand how they actually work, we need to analyze the source code. Start with TypedArray and obtainStyledAttributes(XX) methods. Take a look at the obtainStyledAttributes(xx) call flow:
nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes, parser ! = null ? parser.mParseState : 0, inAttrs, outValuesAddress, outIndicesAddress);Copy the code
Note that the last two parameters correspond to two TypedArray parameters:
outValuesAddress –> int[] mData;
outIndicesAddress –> int[] mIndices
Finally, the ApplyStyle(xx) method of AttributeResolution. CPP is called:
void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, uint32_t def_style_resid, Uint32_t * attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { int indices_idx = 0; uint32_t def_style_flags = 0u; // If (def_styLE_attr! = 0) { Res_value value; If (theme->GetAttribute(def_STYLE_attr, & Value, & def_STYLE_FLAGS)! = kInvalidCookie) {if (value.datatype == Res_value::TYPE_REFERENCE) {// Assign values to default style, Style def_STYLE_resid = value.data; For (size_t ii = 0; size_t ii = 0; size_t ii = 0; ii < attrs_length; If (xml_attr_idx!) {xml_attr_idx! = xml_attr_finder.end()) { // We found the attribute we were looking for. xml_parser->getAttributeValue(xml_attr_idx, &value); } if (value.dataType == Res_value::TYPE_NULL && value.data ! = Res_value::DATA_NULL_EMPTY) {// = xml_style_attr_finder.end()) { // We found the attribute we were looking for. cookie = entry->cookie; type_set_flags = style_flags; value = entry->value; value_source_resid = entry->style; } } if (value.dataType == Res_value::TYPE_NULL && value.data ! Attr ::DATA_NULL_EMPTY) {Res_value::DATA_NULL_EMPTY) {Res_value::DATA_NULL_EMPTY) {Res_value::DATA_NULL_EMPTY); Then load the default style if (Entry! = def_style_attr_finder.end()) { cookie = entry->cookie; type_set_flags = def_style_flags; value = entry->value; } } if (value.dataType ! = Res_value::TYPE_NULL) {// omit} else if (value.data! = Res_value::DATA_NULL_EMPTY) { ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); if (new_cookie ! = kInvalidCookie) {// New_cookie = assetManager ->ResolveReference(new_cookie, & Value, &config, & type_set_FLAGS, &resid); if (new_cookie ! = kInvalidCookie) { cookie = new_cookie; }} //out_values Store type, attribute value, resource ID, and density out_values[STYLE_TYPE] = value.datatype; out_values[STYLE_DATA] = value.data; out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; out_values[STYLE_SOURCE_RESOURCE_ID] = value_source_resid; if (value.dataType ! = Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) { indices_idx++; Indices_idx = indices_indices_idx = indices_idx; } // step size, out_values store attribute values, types, etc., so step size is needed to distinguish the start of an attribute block. Out_indices [0] = indices_idx; }Copy the code
This method is long, omits some places, and does two main things:
1. Steps 1 to 4 above actually determine the priority of the load attribute. 2. Record the queried property values in TypedArray.
Typedarray.getindexcount (): typeDarray.getIndexCount ():
public int getIndexCount() { if (mRecycled) { throw new RuntimeException("Cannot make calls to a recycled instance!" ); } return mIndices[0]; }Copy the code
typedArray.getIndex(i);
Public int getIndex(int at) {public int getIndex(int at) {return mIndices[1+at]; }Copy the code
MIndices [] records the index of attribute names, remember that it was generated in R.Java earlier
public static final int MyStyleable_attr_bool=0;
public static final int MyStyleable_attr_int=1;
public static final int MyStyleable_attr_ref=2;
public static final int MyStyleable_attr_str=3;
Copy the code
The values are recorded as above. These in turn can be indexed to specific attributes:
public static final int[] MyStyleable={
0x7f02002b, 0x7f02002c, 0x7f02002d, 0x7f02002e
};
Copy the code
IndexValue = typeDarray.getIndex (I); typedArray.getString(indexValue); TypedArray int[] mData, which is populated in ApplyStyle above. Typedarray.getindexcount () and typedArray mLength
<com.fish.myapplication.attr.MyAttrView app:attr_str="str in myLayout" app:attr_bool="true" android:layout_width="100px" android:layout_height="100px"> </com.fish.myapplication.attr.MyAttrView> #attrs.xml <declare-styleable name="MyStyleable"> <attr name="attr_str" format="string"></attr> <attr name="attr_bool" format="boolean"></attr> <attr name="attr_int" format="integer"></attr> <attr name="attr_ref" format="reference"></attr> </declare-styleable>Copy the code
We defined only two properties above, while MyStyleable declared four properties, so TypedArray mIndices[] has 2 valid properties. While mLength represents the mIndices[] array length. It is worth noting:
TypedArray instances are reusable, and mIndices[] lengths only get longer. So maybe when you debug mIndices[] is not necessarily equal to 4, it may be larger.
So that’s the analysis of custom attributes.