SoFunction
Updated on 2025-04-10

Detailed explanation of Android custom View constructor

The constructor of the initial Custom View

I wrote a blog that implements a circular progress bar before (Custom circular progress bar), usually when we implement Custom View, we will first inherit the View and implement the three constructors of the View, for example:

import ;
import ;
import ;
import ;

public class MyCustomView extends View {
 /**
   * The first constructor
   */
 public MyCustomView(Context context) {
  this(context, null);
 }

 /**
   * The second constructor
   */
 public MyCustomView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 /**
   * The third constructor
   */
 public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  // TODO: Get custom properties }

 @Override
 protected void onDraw(Canvas canvas) {
  (canvas);
 }
}

There are many statements about the timing of using three constructors on the Internet, but there are not many correct statements. Here I will give you some popular science:

When you directly new a Custom View instance in the code, the first constructor will be called. There is no dispute about this.
When calling Custom View in the xml layout file, the second constructor will be called. There is no controversy about this.
When the Custom View is called in the XML layout file and there are custom properties in the Custom View tag, the second constructor is called here.
That is to say, the system will only call the first two constructors of Custom View by default. As for the call to the third constructor, we usually call actively in the constructor ourselves (for example, calling the third constructor in the second constructor).

As for obtaining custom attributes, they are usually implemented in the constructor through the obtainStyledAttributes function. Here we first introduce how to generate custom attributes of Custom View.

Generate custom properties for Custom View

Custom View adds custom properties mainly by configuring custom properties for it through the declare-styleable tag. The specific method is: add a resource xml file in the res/values/ directory. The example is as follows (res/values/attrs_my_custom_view.xml):

<resources>
 <declare-styleable name="MyCustomView">
  <attr name="custom_attr1" format="string" />
  <attr name="custom_attr2" format="string" />
  <attr name="custom_attr3" format="string" />
  <attr name="custom_attr4" format="string" />
 </declare-styleable>
 <attr name="custom_attr5" format="string" />
</resources>

In the above xml file, we declare a custom attribute set MyCustomView, which contains four attributes: custom_attr1, custom_att2, custom_attr3, and custom_attr4. At the same time, we also declare an independent attribute custom_attr5.

All properties declared in resources files will generate corresponding member variables in the class:

public final class R {
 public static final class attr {
  public static final int custom_attr1=0x7f010038;
  public static final int custom_attr2=0x7f010039;
  public static final int custom_attr3=0x7f01003a;
  public static final int custom_attr4=0x7f01003b;
  public static final int custom_attr5=0x7f010000;
 }
}

However, if the attributes declared in the tag, the system will also generate relevant member variables in the class:

public static final class styleable {
  public static final int[] MyCustomView = {
   0x7f010038, 0x7f010039, 0x7f01003a, 0x7f01003b
  };
  public static final int MyCustomView_custom_attr1 = 0;
  public static final int MyCustomView_custom_attr2 = 1;
  public static final int MyCustomView_custom_attr3 = 2;
  public static final int MyCustomView_custom_attr4 = 3;
}

It can be seen that it is an array, and the element value in it happens to be the value of .custom_attr1~.custom_attr4. The following MyCustomView_custom_attr1~MyCustomView_custom_attr4 is exactly its corresponding index.

After knowing this, we can learn how to get the value of a custom attribute in the constructor of the Custom View.

Get custom properties in the constructor of Custom View

The code for getting custom properties in the third constructor is as follows:

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);

 TypedArray ta = (attrs, );

 String attr1 = (.MyCustomView_custom_attr1);
 String attr2 = (.MyCustomView_custom_attr2);
 String attr3 = (.MyCustomView_custom_attr3);
 String attr4 = (.MyCustomView_custom_attr4);

 ("customview", "attr1=" + attr1);
 ("customview", "attr2=" + attr2);
 ("customview", "attr3=" + attr3);
 ("customview", "attr4=" + attr4);
 ();
 }

Regarding the acquisition of custom attributes, we mainly call this function. I believe that everyone is very skilled in using this function when customizing the View. Let’s take a look at the source code implementation of this function:

public final TypedArray obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs) {
 return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
}

Through tracking the source code, we found that the obtainedStyledAttributes method of the two parameters of the context finally calls the obtainedStyledAttributes method of the 4 parameters of Theme. Let's take a look at the source code implementation of this function:

public TypedArray obtainStyledAttributes(AttributeSet set,
  @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
 final int len = ;
 final TypedArray array = (, len);

 // XXX note that for now we only work with compiled XML files.
 // To support generic XML files we will need to manually parse
 // out the attributes from the XML file (applying type information
 // contained in the resources and such).
 final  parser = ()set;
 (mTheme, defStyleAttr, defStyleRes,
   parser != null ?  : 0, attrs, , );

  = this;
  = parser;

 if (false) {
  int[] data = ;

  ("Attributes:");
  String s = " Attrs:";
  int i;
  for (i=0; i<(); i++) {
   s = s + " " + (i);
   int id = (i);
   if (id != 0) {
    s = s + "(0x" + (id) + ")";
   }
   s = s + "=" + (i);
  }
  (s);
  s = " Found:";
  TypedValue value = new TypedValue();
  for (i=0; i<; i++) {
   int d = i*AssetManager.STYLE_NUM_ENTRIES;
    = data[d+AssetManager.STYLE_TYPE];
    = data[d+AssetManager.STYLE_DATA];
    = data[d+AssetManager.STYLE_ASSET_COOKIE];
    = data[d+AssetManager.STYLE_RESOURCE_ID];
   s = s + " 0x" + (attrs[i])
    + "=" + value;
  }
  (s);
 }

 return array;
}

I won’t explain too much source code here, but explain the meaning of these four parameters to everyone:

AttributeSet set: A collection of attribute values.

int[] attrs: Our custom attribute collection generates an int-type array in the R class. This array contains the resource ID of the custom attribute.
int defStyleAttr: This is a reference to style contained in the current Theme. When we do not set a declare-styleable resource collection for a custom View, the default setting attribute value is found in the layout file from this collection. Passing 0 means that the default value is not found in the defStyleAttr.
int defStyleRes: This is also a resource ID pointing to Style, but it only works if defStyleAttr is 0 or defStyleAttr is not 0 but the defStyleAttr property is not assigned to the defStyleAttr property in Theme.

Since an attribute can be assigned to it in many places, including: XML layout files, decalare-styleable, theme, etc., there is a priority order between them, and the following is sorted from high to low according to priority:

Attribute assignment priority order table:

Define directly in layout xml > Define through style in layout xml > Specify style reference in the Theme of the Activity where the View is located > DefStyleRes specified in the constructor

In order to give everyone a clearer and more intuitive understanding, in the next chapter of setting custom properties, I will define the four properties of custom_attr1~4 in the above four places, and then get its value in the constructor of Custom View to see if the priority order is the same as we expected.

Set custom attribute values

The following parameters are all explained by the four-parameter constructor of the obtainedStyledAttributes for the class.

The second parameter——Assign attribute values ​​in the layout xml file

Before setting custom properties, we first need to call our Custom View in the layout file of the main activity and set specific properties for it.

The content of the main layout file is as follows:

<FrameLayout xmlns:andro
 xmlns:custom="/apk/res-auto"
 android:
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <
  android:
  android:layout_width="400dp"
  android:layout_height="400dp"
  custom:custom_attr1="attr1_xml"
  style="@style/TestCustomView"/>

</FrameLayout>

Example results:

05-28 17:19:56.542 23575-23575/? E/customview: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customview: attr2=null
05-28 17:19:56.542 23575-23575/? E/customview: attr3=null
05-28 17:19:56.542 23575-23575/? E/customview: attr4=null

Notice:

When assigning values ​​to custom attributes, you need to first add the namespace of the custom attributes. For example: xmlns:custom="/apk/res-auto". Android Studio recommends using res-auto. In Eclipse, you need to use the package name where the Custom View is located: xmlns:cv="/apk/"
Here, in the layout file, we assign the value to custom_attr1 as: attr1_xml. The custom view gets the attribute value corresponding to the second parameter in the getTheme().obtainStyledAttributes method @StyleableRes int[] attrs

The second parameter——Assign value to attributes in style

Secondly, custom attributes can also be assigned values ​​in Style.

First, we also add a custom style to MyCustomView in the xml layout file, and the style code is as follows:

<style name="TestCustomView">
 <item name="custom_attr1">attr1_style</item>
 <item name="custom_attr2">attr2_style</item>
</style>

Then, we modify the layout file and add the style field:

<FrameLayout xmlns:andro
 xmlns:custom="/apk/res-auto"
 android:
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <
  android:
  android:layout_width="400dp"
  android:layout_height="400dp"
  custom:custom_attr1="attr1_xml"
  style="@style/TestCustomView"/>

</FrameLayout>

Example results:

05-28 17:19:56.542 23575-23575/? E/customview: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customview: attr2=attr2_style
05-28 17:19:56.542 23575-23575/? E/customview: attr3=null
05-28 17:19:56.542 23575-23575/? E/customview: attr4=null

Here we assign the custom_attr1 attribute again, and we also assign custom_attr2.

Tips:

Smart students must have guessed the role of my assignment, but I still need to briefly describe it:
For custom_attr1, we assign values ​​in the xml layout file, style, defStyle and theme, and the final result will inevitably confirm who has the highest priority.
For custom_attr2, we assign values ​​in style, defStyle and theme. Through the result we can know who has the second highest priority.
I won't explain much about the assignment of custom_attr3 and custom_attr4, I believe everyone understands it! !
At the same time, it is also important to note that as long as it is in the layout layout file, whether it is directly assigned to the attribute through namespace or assigning to the attribute through style, the second parameter in the getTheme().obtainStyledAttributes method @StyleableRes int[] attrs

The third parameter defStyleAttr

This parameter means:

Original text: An attribute in the current theme that contains areference to a style resource that supplies defaults values ​​for the TypedArray. Can be 0 to not look for defaults.
Translation: This is a reference to style contained in the current Theme. When we do not set a declare-styleable resource collection for a custom View, the attribute value is configured from the layout file by default. Passing 0 means that the default value is not found in the defStyleAttr.
In order to test the function and priority of this parameter, we need to perform the following operations.

First, we first declare a reference to the style. Declare it in the previous res/values/attrs_my_custom_view.xml file:

<attr name="MyCustomViewDefStyleAttr" format="reference"/>

Then, the theme used by the Activity containing the custom View is:

<activity>
 android:name=""
 android:theme="@style/AppTheme"
 android:label="@string/app_name" >
 <intent-filter>
  <action android:name="" />
  <category android:name="" />
 </intent-filter>
</activity>

Finally, add the reference implementation of MyCustomViewDefStyleAttr under the AppTheme theme in it.

<style name="AppTheme" parent="android:">
 <!-- Customize your theme here. -->
 <item name="MyCustomViewDefStyleAttr">@style/MyCustomViewDefStyleAttrImpl</item>
</style>

<style name="MyCustomViewDefStyleAttrImpl">
 <item name="custom_attr1">attr1_defStyleAttr</item>
 <item name="custom_attr2">attr2_defStyleAttr</item>
 <item name="custom_attr3">attr3_defStyleAttr</item>
</style>

Code verification, remember that if you want to use the third parameter of the obtainedStyledAttributes method, you need to call getTheme() in the third constructor.

public MyCustomView(Context context, AttributeSet attrs) {
 // Assign values ​​to defStyleAttr this(context, attrs, );
}

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);

 TypedArray ta = ().obtainStyledAttributes(attrs, , defStyleAttr, 0);

 String attr1 = (.MyCustomView_custom_attr1);
 String attr2 = (.MyCustomView_custom_attr2);
 String attr3 = (.MyCustomView_custom_attr3);
 String attr4 = (.MyCustomView_custom_attr4);

 ("customview", "attr1=" + attr1);
 ("customview", "attr2=" + attr2);
 ("customview", "attr3=" + attr3);
 ("customview", "attr4=" + attr4);
 ();
}

There are two points in the code that need to be paid attention to:

I have assigned defStyleAttr in the second construct parameter, and the third construct parameter can be directly used to pass in the parameter.
In the third constructor parameter, I used the obtainedStyledAttributes method of getTheme() to replace the obtainedStyledAttributes constructor of the 2 parameters of the context.
Example results:

05-28 17:19:56.542 23575-23575/? E/customview: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customview: attr2=attr2_style
05-28 17:19:56.542 23575-23575/? E/customview: attr3=attr3_defStyleAttr
05-28 17:19:56.542 23575-23575/? E/customview: attr4=null

As can be seen from the results, the priority of specifying a style reference in the topic is lower than the direct assignment of values ​​in xml and the use of style fields.

At the same time, we need to know one more thing:
In Android systems, many controls use a third parameter in constructing parameters, such as Button. The advantage of this is that when we switch different themes, the style of Button can also be changed.

The fourth parameter——Assign value to attributes through defStyleRes

This parameter means:

Original text: A resource identifier of a style resource that supplies default values ​​for the TypedArray, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.
Translation: This is a resource ID pointing to Style, but it only works if defStyleAttr is 0 or defStyleAttr is not 0 but the defStyleAttr property is not assigned to the defStyleAttr property in Theme.
Through translation, we can clarify two points:

: Point to a style reference.
The priority is lower than defStyleAttr.
To verify, we first define a style in the file:

<style name="MyCustomViewDefStyleRes">
 <item name="custom_attr1">attr1_defStyleRes</item>
 <item name="custom_attr2">attr2_defStyleRes</item>
 <item name="custom_attr3">attr3_defStyleRes</item>
 <item name="custom_attr4">attr4_defStyleRes</item>
</style>

Then, we assign values ​​in the obtainedStyledAttributes function in the third constructor of the custom View. The specific method is as follows:

public MyCustomView(Context context, AttributeSet attrs) {
 this(context, attrs, );
}

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);

 // Assign values ​​to defStyleRes TypedArray ta = ().obtainStyledAttributes(attrs, , defStyleAttr, );

 String attr1 = (.MyCustomView_custom_attr1);
 String attr2 = (.MyCustomView_custom_attr2);
 String attr3 = (.MyCustomView_custom_attr3);
 String attr4 = (.MyCustomView_custom_attr4);

 ("customview", "attr1=" + attr1);
 ("customview", "attr2=" + attr2);
 ("customview", "attr3=" + attr3);
 ("customview", "attr4=" + attr4);
 ();
}

Test results:

05-28 17:44:09.282 3137-3137/? E/customview: attr1=attr1_xml
05-28 17:44:09.282 3137-3137/? E/customview: attr2=attr2_style
05-28 17:44:09.282 3137-3137/? E/customview: attr3=attr3_defStyleAttr
05-28 17:44:09.282 3137-3137/? E/customview: attr4=null

Key points:
If you look at the experimental results carefully, you will definitely be surprised by the above results. DefStyleRes is clearly specified, why is the value of attr4 still null?
This is because I mentioned before that the usage priority of defStyleRes: it will only work if defStyleAttr is 0 or if the current Theme is not assigned a value to the defStyleAttr attribute.

So, here we need to modify the constructor and set defStyleAttr to 0.

public MyCustomView(Context context, AttributeSet attrs) {
 // To verify the effect of defStyleRes, set defStyleAttr to 0 this(context, attrs, 0);
}

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);

 TypedArray ta = ().obtainStyledAttributes(attrs, , defStyleAttr, );

 String attr1 = (.MyCustomView_custom_attr1);
 String attr2 = (.MyCustomView_custom_attr2);
 String attr3 = (.MyCustomView_custom_attr3);
 String attr4 = (.MyCustomView_custom_attr4);

 ("customview", "attr1=" + attr1);
 ("customview", "attr2=" + attr2);
 ("customview", "attr3=" + attr3);
 ("customview", "attr4=" + attr4);
 ();
}

Final result:

05-28 17:49:03.707 5772-5772/? E/customview: attr1=attr1_xml
05-28 17:49:03.707 5772-5772/? E/customview: attr2=attr2_style
05-28 17:49:03.707 5772-5772/? E/customview: attr3=attr3_defStyleRes
05-28 17:49:03.707 5772-5772/? E/customview: attr4=attr4_defStyleRes

postscript

At the end of the article, we will summarize the attribute assignment priority of custom attributes again:

Define directly in the layout xml > Define through style in the layout xml > Specify the style reference in the Theme of the Activity where the View is located > DefStyleRes specified in the constructor.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.