A deep dive into Android View constructors

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?

tl;dr

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.
  • 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:

  • Context - Used all over the place in Views.
  • AttributeSet - The XML attributes (when inflating from XML).
  • int defStyleAttr - A default style to apply to the View (defined in the theme).
  • 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).

Attributes

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

<ImageView  
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:src="@drawable/icon"
  />

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>. 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... -->

</declare-styleable>  

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.

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.

AttributeSet

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.

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);
  setImageDrawable(src);
  ta.recycle();
}

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).

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).

You can read more about extracting attributes in the official documentation.

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.

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:

<resources>  
  <declare-styleable name="Theme">

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

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

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

  </declare-styleable>
</resource>  

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:

<resources>  
  <style name="Theme">

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

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

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

  </style>
</resource>  

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

<activity  
  android:name=".MyActivity"
  android:theme="@style/Theme"
  />

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).
  5. 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)

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).

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

comments powered by Disqus