Android 中View的四个构造函数

I often see confusion around Android View constructors. Why are there four of them? What does each parameter do? Which constructors do I need to implement?


If you just want quick, practical advice, here's a few good guidelines:

  • Use View(Context) for creating Views in code.
    Override View(Context, AttributeSet) when inflating Views from XML.
    在代码中使用View(Context)来创建View,如果要用在XML文件中就需要重写 View(Context, AttributeSet)构造函数。
  • Ignore the rest because you probably won't need them.

For those still with me - let's dive in.

Constructor parameters
At most, there can be four constructor parameters. A brief summary:

  1. Context - Used all over the place in Views.
  2. AttributeSet - The XML attributes (when inflating from XML).
  3. int defStyleAttr - A default style to apply to the View (defined in the theme).
  4. int defStyleResource - A default style to apply to the View, if defStyleAttr is unused.

Besides the Context, the other parameters are only used to configure the initial state of the View based on XML attributes (from layout, styles and themes).


Let's start by talking about how you define valid XML attributes. Here's a basic ImageView in XML:


Have you ever wondered where layout_width, layout_height and src come from? It's not out of thin air; you actually declare these attributes explicitly as something the system should handle via <declare-styleable>

你是否好奇layout_width, layout_height and src从哪里来,它并不是无中生有;它在系统的<declare-styleable中声明了

For example, here's where src is defined:

<declare-styleable name="ImageView">  
  <!-- Sets a drawable as the content of this ImageView. -->
  <attr name="src" format="reference|color" />

  <!-- ...snipped for brevity... -->


Each declare-styleable generates one R.styleable.[name] plus an additional R.styleable.[name]_[attribute] for each attribute. For example, the above generates R.styleable.ImageView and R.styleable.ImageView_src.


What are these resources? The base R.styleable.[name] is an array of all the attribute resources, which the system uses to lookup attribute values. Each R.styleable.[name]_[attribute] is just an index into that array, so that you can retrieve all the attributes at once, then lookup each value individually.

这些资源是什么?R.styleable.[name]是个数组,里面包含了所有的属性资源,系统用它来寻找属性所对应的值(也就是资源)。每一个R.styleable.[name]_[attribute] 只是上面数组里面的索引(index),通过它就可以获得所有属性的值。

If you think of it like a cursor, you can consider R.styleable.[name] as list of columns to query and each R.styleable.[name]_[attribute] as a column index.


For more on declare-styleable, here's the official documentation on creating your own.

想要了解更多有关declare-styleable 可以去查看官方文档


The XML we wrote above is given to the View as an AttributeSet.

Usually you don't access AttributeSet directly, but instead use Theme.obtainStyledAttributes(). That's because the raw attributes often need to resolve references and apply styles. For example, if you define style=@style/MyStyle in your XML, this method resolves MyStyle and adds its attributes to the mix. In the end, obtainStyledAttributes() returns a TypedArray which you can use to access the attributes.

通常你不会直接去访问AttributeSet,而是通过使用Theme.obtainStyledAttributes()方法去访问。这是因为这些attributes需要去解析引用和应用样式。例如,如果你在你的XML中定义了style=@style/MyStyle,这个方法可以解析MyStyle并且把它的attributes添加到属性集合中。最后obtainStyledAttributes() 返回一个TypedArray,通过TypedArray可以访问这些attributes。

Greatly simplified, the process looks like this:

public ImageView(Context context, AttributeSet attrs) {  
  TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ImageView, 0, 0);
  Drawable src = ta.getDrawable(R.styleable.ImageView_src);

In this case, we're passing two parameters to obtainStyledAttributes(). The first is AttributeSet attrs, the attributes from XML. The second is the array R.styleable.ImageView, which tells the method which attributes we want to extract (and in what order).

在这里,我们传了两个参数给obtainStyledAttributes()函数,第一参数就是AttributeSet attrs,是所有来源于XML的attributes。第二个参数就是R.styleable.ImageView数组,它告诉了这个方法那些attributes我们想要去提取(比如src属性,而不是sra其他属性)。

With the TypedArray we get back, we can now access the individual attributes. We need to use R.styleable.ImageView_src so that we correctly index the attribute in the array.
(Recycling TypedArrays is important, too, so I left it in the sample above.)

Normally you extract multiple attributes at once. Indeed, the actual ImageView implementation is far more complex than what's shown above (since ImageView itself has many more attributes it cares about).

Theme Attributes

A sidenote, for completeness: The AttributeSet is not the only place we got our values from when using obtainStyledAttributes() in the last section. Attributes can also exist in the theme.


This rarely plays a role for View inflation because your theme shouldn't be setting attributes like src, but it can play a role if you use obtainStyledAttributes() for retrieving theme attributes (which is useful but is outside the scope of this article).

Default Style Attribute

You may have noticed that I used 0 for the last two parameters in obtainStyledAttributes(). They are actually two resource references - defStyleAttr and defStyleRes. I'm going to focus on the first one here.

你可能已经注意到在obtainStyledAttributes()函数中我使用了0来作为后面两个参数的值。这两个参数实际上是两个资源的引用--defStyleAttr 和 defStyleRes。后面我将重点放在第一个参数(defStyleAttr )上。

defStyleAttr is, by far, the most confusing parameter for obtainStyledAttributes(). According to the documentation it is:


An attribute in the current theme that contains a reference to a style resource that supplies defaults values for the TypedArray.

Whew, that's a mouthful. In plain English, it's a way to be able to define a base style for all Views of a certain type. For example, you can set textViewStyle in your theme if you want to modify all your app's TextViews at once. If this didn't exist, you'd have to manually style every TextView instead.


Let's walk through how it actually works, using TextView as an example.

First, it's an attribute (in this case, R.attr.textViewStyle). Here's where the Android platform defines textViewStyle:
首先,textViewStyle是一个属性,对应索引就是 R.attr.textViewStyle, 下面就是textViewStyle属性的定义。

  <declare-styleable name="Theme">

    <!-- ...snip... -->

    <!-- Default TextView style. -->
    <attr name="textViewStyle" format="reference" />

    <!-- ...etc... -->


Again, we're using declare-styleable, but this time to define attributes that can exist in the theme. Here, we're saying that textViewStyle is a reference - that is, its value is just a reference to a resource. In this case, it should be a reference to a style.


Next we have to set textViewStyle in the current theme. The default Android theme looks like this:

  <style name="Theme">
    <!-- ...snip... -->

    <item name="textViewStyle">@style/Widget.TextView</item>

    <!-- ...etc... -->


Then the theme has to be set for your Application or Activity, typically via the manifest:


Now we can use it in obtainStyledAttributes():

 TypedArray ta = theme.obtainStyledAttributes(attrs, R.styleable.TextView, R.attr.textViewStyle, 0);  

The end result is that any attributes not defined by the AttributeSet are filled in with the style that textViewStyle references.

Phew! Unless you're being hardcore, you don't need to know all these implementation details. It's mostly there so that that the Android framework can let you define base styles for various Views in your theme.

Default Style Resource

defStyleRes is much simpler than its sibling. It is just a style resource (i.e. @style/Widget.TextView). No complex indirection through the theme.


The attributes from the style in defStyleRes are applied only if defStyleAttr is undefined (either as 0 or it isn't set in the theme).


Precedence 优先级

We've now got a bunch of ways to derive the value for an attribute via obtainStyledAttributes(). Here's their order of precedence, from highest to lowest:

  1. Any value defined in the AttributeSet.
  2. The style resource defined in the AttributeSet (i.e. style=@style/blah).
  3. The default style attribute specified by defStyleAttr.
  4. The default style resource specified by defStyleResource (if there was no defStyleAttr).
    Values in the theme.

In other words, any attributes you set directly in XML will be used first.
But there are all sorts of other places those attributes can be retrieved from if you don't set them yourself.

View constructors
This article was supposed to be about View constructors, right?

There are four of them total, each one adding a parameter:


View(Context, AttributeSet)

View(Context, AttributeSet, defStyleAttr)

View(Context, AttributeSet, defStyleAttr, defStyleRes)
An important note: the last one was added in API 21, so you unless you've got minSdkVersion 21, you should avoid it for now. (If you want to use defStyleRes just call obtainStyledAttributes() yourself since it's always been supported.)


They cascade, so if you call one, you end up calling them all (via super). The cascading also means you only need to override the constructors you use. Generally, this means that you only need to implement the first two (one for code constructor, the other for XML inflation).


I usually setup my custom Views like so:

SomeView(Context context) {  
  this(context, null);

SomeView(Context context, AttributeSet attrs) {  
  // Call super() so that the View sets itself up properly
  super(context, attrs);

  // ...Setup View and handle all attributes here...

Within the two-arg constructor you can use obtainStyledAttributes() any way you want. A quick way to implement a default style is to just provide defStyleRes to it; that way you don't need to go through the pain in the butt that is defStyleAttr (which is more of a framework tool and not usually necessary for a single app).
你可以在带两个参数的构造函数中以你想要的方式来使用obtainStyledAttributes() 函数,一种快速的方法就是提供一个默认的defStyleRes来这个函数,这样我们就可以避免使用defStyleAttr(他需要在Theme声明属性的值)所带来的痛苦。

Anyways, I hope this helps not only your understanding of View constructors but also how attributes are retrieved during View construction!