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 creatingViews
in code. - Override
View(Context, AttributeSet)
when inflatingViews
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 inViews
.AttributeSet
- The XML attributes (when inflating from XML).int defStyleAttr
- A default style to apply to theView
(defined in the theme).int defStyleResource
- A default style to apply to theView
, ifdefStyleAttr
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()
](https://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int)). 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:
- Any value defined in the
AttributeSet
. - The style resource defined in the
AttributeSet
(i.e.style=@style/blah
). - The default style attribute specified by
defStyleAttr
. - The default style resource specified by
defStyleResource
(if there was nodefStyleAttr
). - 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!