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?
我經(jīng)常對View的構(gòu)造函數(shù)感到很困惑暮屡,為什么會有四個醋旦?每個參數(shù)表示什么妨托?我應(yīng)該需要實現(xiàn)那些構(gòu)造函數(shù)瞳秽?
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)來創(chuàng)建View雁仲,如果要用在XML文件中就需要重寫 View(Context, AttributeSet)構(gòu)造函數(shù)。 - Ignore the rest because you probably won't need them.
忽略其他兩個構(gòu)造函數(shù)咆畏,你可能不需要他們。
For those still with me - let's dive in.
Constructor parameters
At most, there can be four constructor parameters. A brief summary:
基本上吴裤,存在四個構(gòu)造函數(shù)參數(shù)旧找,一個簡潔的總結(jié)
- Context - Used all over the place in Views.
上下文 - AttributeSet - The XML attributes (when inflating from XML).
XML屬性 - int defStyleAttr - A default style to apply to the View (defined in the theme).
一個默認(rèn)的風(fēng)格應(yīng)用到View,在theme中定義麦牺。 - int defStyleResource - A default style to apply to the View, if defStyleAttr is unused.
一個默認(rèn)的風(fēng)格應(yīng)用到View钮蛛,如果defStyleAttr沒有使用。
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).
除了Context剖膳,其他參數(shù)都是通過XML來配置View的初始狀態(tài)魏颓。(來自于layout,styles,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>
。
你是否好奇layout_width, layout_height and src從哪里來吱晒,它并不是無中生有甸饱;它在系統(tǒng)的<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.
每一個declare-styleable都會生成一個R.styleable.[name],節(jié)點(diǎn)里面的每個屬性(attribute)都會生成一個R.styleable.[name]_[attribute]的資源仑濒,例如叹话,上面代碼,會生成一個R.styleable.ImageView
和 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]
是個數(shù)組驼壶,里面包含了所有的屬性資源,系統(tǒng)用它來尋找屬性所對應(yīng)的值(也就是資源)喉酌。每一個R.styleable.[name]_[attribute]
只是上面數(shù)組里面的索引(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.
你可以把它想象成一個cursor,把R.styleable.[name]
當(dāng)成要查詢字段的集合泪电,R.styleable.[name]_[attribute]
就是這些字段的索引般妙。
For more on declare-styleable, here's the official documentation on creating your own.
想要了解更多有關(guān)declare-styleable
可以去查看官方文檔。
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.
通常你不會直接去訪問AttributeSet歪架,而是通過使用Theme.obtainStyledAttributes()方法去訪問股冗。這是因為這些attributes需要去解析引用和應(yīng)用樣式。例如和蚪,如果你在你的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);
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).
在這里,我們傳了兩個參數(shù)給obtainStyledAttributes()函數(shù)集峦,第一參數(shù)就是AttributeSet attrs伏社,是所有來源于XML的attributes。第二個參數(shù)就是R.styleable.ImageView
數(shù)組塔淤,它告訴了這個方法那些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.
通過使用返回的TypedArray對象聪黎,我們可以去訪問這些獨(dú)立的attributes。我們需要使用R.styleable.ImageView_src
作為index來正確的提取該屬性(src屬性)所對應(yīng)的值(圖片的引用)
(Recycling TypedArrays is important, too, so I left it in the sample above.)
回收TypedArrays是非常重要的备恤,所以我在上面的代碼中寫上了稿饰。
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).
通常你會一次性提取多個attributes。確實露泊,實際上ImageView的實現(xiàn)比上面代碼要復(fù)雜的多喉镰。
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.
AttributeSet并不是obtainStyledAttributes()獲取值得唯一來源,Attributes(屬性)也可以來源于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).
如果你打算從theme中獲取的屬性來初始化View侣姆,那么這種情況是很少見的,畢竟我們通常不會在theme中設(shè)置src這樣的屬性脖咐,但是如果你是想使用obtainStyledAttributes()來檢索theme的屬性铺敌,它確實有點(diǎn)作用。不過這不在本文的討論范圍內(nèi)屁擅。
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.
你可能已經(jīng)注意到在obtainStyledAttributes()函數(shù)中我使用了0來作為后面兩個參數(shù)的值偿凭。這兩個參數(shù)實際上是兩個資源的引用--defStyleAttr 和 defStyleRes。后面我將重點(diǎn)放在第一個參數(shù)(defStyleAttr )上派歌。
defStyleAttr is, by far, the most confusing parameter for obtainStyledAttributes(). According to the documentation it is:
defStyleAttr是obtainStyledAttributes()函數(shù)中最讓人感到困惑的參數(shù)弯囊。下面是官方文檔的說明。
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.
它是一種可以讓同一類型的所有View具有基本的樣式胶果。例如匾嘱,你如果想要一次性的改變你App中所有TextView的樣式,你可以在theme中設(shè)置textViewStyle屬性早抠。如果theme沒有設(shè)置的話霎烙,你就必須手動的改變每個TextView。
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是一個屬性悬垃,對應(yīng)索引就是 R.attr.textViewStyle游昼, 下面就是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.
再一次我們使用了declare-styleable尝蠕,但是這次我們定義的屬性可以在Theme中使用烘豌。在這里textViewStyle值的類型是一個引用類型。所以它對應(yīng)的值就是資源的引用看彼。本文中它引用了一個style廊佩。
Next we have to set textViewStyle in the current theme. The default Android theme looks like this:
接下來我們在當(dāng)前theme中使用textViewStyle屬性,默認(rèn)的Android主題類似下面的代碼:
<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:
然后要按如下的方式在manifest中設(shè)置給你的應(yīng)用或者Activity靖榕。
<activity
android:name=".MyActivity"
android:theme="@style/Theme"
/>
Now we can use it in obtainStyledAttributes():
現(xiàn)在我們可以在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.
結(jié)果就是那些沒有在AttributeSet中定義的屬性标锄,都會通過textViewStyle引用來填充這些屬性的值。
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.
具體細(xì)節(jié)不用了解的太清楚茁计。大多數(shù)情況下鸯绿,Android框架可以讓你在主題中為各種View定義基本樣式。
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.
這個就更簡單了簸淀,它就是一個style資源。
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).
defStyleRes所引用的style中的屬性毒返,只有在defStyleAttr沒有用到的情況下才會被使用租幕。
Precedence 優(yōu)先級
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:
現(xiàn)在通過obtainStyledAttributes()方法我們有幾種方式去獲取屬性的值,那么這些的途徑的優(yōu)先級從高到低如下排列
- 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 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.
也就是首先使用你在XML文件中設(shè)置的屬性拧簸。其次才從其他地方獲取那些沒有XML中設(shè)置的屬性的值
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.)
最后一個構(gòu)造函數(shù)是在API21的時候才被添加進(jìn)去劲绪。所以除非你的minSdkVersion為21,否則不要使用它盆赤。
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).
他們是串聯(lián)的贾富。所以你可以只在帶兩個參數(shù)的構(gòu)造函數(shù)中實現(xiàn)邏輯,在另外一個構(gòu)造函數(shù)中使用this()來調(diào)用帶兩個參數(shù)的構(gòu)造函數(shù)即可牺六。
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).
你可以在帶兩個參數(shù)的構(gòu)造函數(shù)中以你想要的方式來使用obtainStyledAttributes() 函數(shù),一種快速的方法就是提供一個默認(rèn)的defStyleRes來這個函數(shù)淑际,這樣我們就可以避免使用defStyleAttr(他需要在Theme聲明屬性的值)所帶來的痛苦畏纲。
Anyways, I hope this helps not only your understanding of View constructors but also how attributes are retrieved during View construction!