译自:http://blog.danlew.net/2016/07/19/a-deep-dive-into-android-view-constructors/
曾几何时,我经常对Android View构造函数感到疑惑。为什么有四个构造函数?每个参数做什么?我需要实现哪些构造函数?
TL,DR:
如果你想要快速实用的建议,只需关注以下几点:
1、使用 单参构造方法 View(Context)在代码中创建Views。
2、当需要从Xml挂载视图Views的时候重写View(Context, AttributeSet)。
3、忽略其他你不需要的部分。
注意了,还在车上的人,拉好扶手,开车了。
Constructor parameters 构造函数参数
构造函数最多有四个参数,简单的说,他们就是:
Context - 用于视图中的所有位置(推荐持有Activity context);
AttributeSet - Xml属性(挂载xml视图);
int defStyleAttr - 应用于View的默认样式(定义于Theme中);
int defStyleResource - 如果defStyleAttr未使用,则应用于View的默认样式;
除了Context ,其他的参数仅用于根据Xml属性(布局、样式。主题)配置视图的初始状态。
Attributes 属性
我们先来谈谈如何定义有效的Xml属性。看XML中ImageView 基本的属性:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon"/>
有没有想过 layout_width 、layout_height、src从何而来?当然最开始我是没有想过的,反正拿起来用就行。后来发现要耍女朋友就要买车房,MD,家不在雄安,要来钱就只有提高自己了,扯淡了。当然那些属性不是凭空而来,它实际上是系统通过处理<declare-styleable>来声明的。例如:src 就是这样定义的:
<declare-styleable name = "ImageView">
<attr name = "src" format="reference|color"/>
<!-- ...snipped for brevity...-->
</declare-styleable>
每个声明样式都会为每个属性生成一个R.styleable.[name] 和一个R.styleable.[name]_[attribute]。比如, 上面的src样式 就生成R.styleable.ImageView 和 R.styleable.ImageView_src。
当然那R.styleable.[name] 和一个R.styleable.[name]_[attribute] 都是什么玩意呢?注意了,那就是:R.styleable.[name]就是所有属性资源的数组,系统用它来查找属性值。而每个一个R.styleable.[name]_[attribute]只是该数组中的一个索引,因此,你可以一次性检索所有的属性,然后单独查找每个值。当然你也可以把它想象成一个 cursor,R.styleable.[name] 作为查询列,而R.styleable.[name]_[attribute]就是列索引。
关于更多的declare-styleable ,参见官方文档 the official documentation。
AttributeSet 属性集
我们上面写的Xml被赋予给View作为AttributeSet 属性集。
通常我们不直接访问AttributeSet,而是使用Theme.obtainStyledAttributes()。这是因为原始属性通常需要解析引用并应用样式。例如,如果你在XML中定义style = @ style / MyStyle,则此方法可解析MyStyle,并将其属性添加到属性集中。最后,我们可以通过getsStyledAttributes()返回一个TypedArray,用它来访问相应属性。
不喜欢看文字,哥也不喜欢,上代码:
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();
}
在这种情况下,我们传递两个参数来获取StateedAttributes()。第一个是AttributeSet attrs,来自XML的属性。第二个是数组R.styleable.ImageView,它告诉方法我们要提取哪些属性(以什么顺序)。
如上,我们取得返回的TypedArray,我们现在可以访问各个属性。我们需要使用R.styleable.ImageView_src,以便我们正确地索引数组中的属性。
(回收TypedArray也是非常重要的! 非常重要! 非常重要! ,3遍了哈,如上,我可是加上了的。。。)
通常您可以一次提取多个属性。事实上,ImageView实现比上面我们看见的复杂得多(因为ImageView本身有更多的需要关注的属性)。
想了解更多,参见官方文档 the official documentation。
Theme Attributes 主题属性
旁注,为了完整性:在最后一节中使用 getsStyledAttributes()时,AttributeSet 不是我们获取值的唯一的地方。属性也可以存在于主题中。
对于View的挂载,这个很少起到作用。因为你的主题不应该像src那样设置属性,但是如果您使用getsStyledAttributes()来检索主题属性(这是有用的,但不属于本文的范围),则可以发挥作用。
Default Style Attribute 默认样式属性
你可能已经注意到我在getsStyledAttributes()中为最后两个参数传入了0。它们实际上是两个资源引用 - defStyleAttr和defStyleRes。我将重点关注第一个,也就是defStyleAttr。
到目前为止,defStyleAttr是getsStyledAttributes()最令人困惑的参数。根据文档说明:
An attribute in the current theme that contains a reference to a style resource that supplies defaults values for the TypedArray.
wow,真绕口。还是讲人话吧,这是一种能够为某种类型的所有视图定义基本样式的方式。例如,如果要一次修改所有应用程序的TextView,您可以在主题中设置textViewStyle。如果不这样做,那么您必须手动为每个TextView设置样式。
这里以TextView为例,介绍实际工作原理。
首先,它是一个属性(在这种情况下为R.attr.textViewStyle)。这里是Android平台定义textViewStyle的地方:
<resources>
<declare-styleable name="Theme">
<!-- ...snip... -->
<!-- Default TextView style.-->
<attr name="textViewStyle" format="reference"/>
<!-- ...etc... -->
</declare-styleable>
</resources>
再次,我们使用declare-styleable,但这次是定义可以存在于主题中的属性。在这里,我们说textViewStyle是一个引用 - 也就是说,它的值只是一个对资源的引用。在这种情况下,它应该是一种风格的引用。(怎么感觉拗口,是时候多看看书学习下了......)
接下来我们必须在当前主题中设置textViewStyle。默认的Android主题如下所示:
<resources>
<declare-styleable name="Theme">
<attr name="textViewStyle" format="reference"/>
</declare-styleable >
</resources>
然后,您的Application或activity必须为主题设置,通常通过清单:
<activity
android:name=".MyActivity"
android:theme="@style/Theme" />
现在我们可以在getsStyledAttributes()中使用它:
TypedArray ta = theme.obtainStyledAttributes(attrs, R.styleable.TextView, R.attr.textViewStyle, 0);
最终结果是:没有由AttributeSet定义的任何属性的都使用textViewStyle引用的样式来填充。
除非你是核心人物,否则你不需要知道所有这些实现细节。它之所以在就是方便Android框架在主题中定义各种视图的基本样式。
Default Style Resource 默认风格资源
defStyleRes比其兄弟姐妹更简单。它只是一种风格资源(即@ style / Widget.TextView)。
defStyleRes中样式的属性只有在defStyleAttr未定义(0或未在主题中设置)时才会应用。
Precedence 优先
现在我们已经有一堆通过getsStyledAttributes()来获取属性的值方法。这是他们的优先顺序,从最高到最低:
1、AttributeSet中定义的任何值。
2、AttributeSet中定义的样式资源(即style = @ style / blah)。
3、由defStyleAttr指定的默认样式属性。
4、由defStyleResource指定的默认样式资源(如果没有指定defStyleAttr)。
5、主题中设置的值
换言之,您在xml中直接设置的任何属性将首先使用。但是如果你不自己设置的话,这些属性可以从其他地方检索到。
View constructors 构造函数
这篇文章应该是关于View构造函数的,对吧?
共有四个,每个增加一个参数:
View(Context)
View(Context, AttributeSet)
View(Context, AttributeSet, defStyleAttr)
View(Context, AttributeSet, defStyleAttr, defStyleRes)
一个重要的注意事项:最后一个添加到API 21中,所以除非minSdkVersion == 21,否则现在应该避免它。 (如果你想使用defStyleRes,只要自己调用getsStyledAttributes(),因为它始终被支持。)
他们级联,所以如果你调用一个,你最终调用他们所有(通过super)。级联也意味着你只需要重写你使用的构造函数。一般来说,这意味着你只需要实现前两个(一个用于代码构造函数,另一个用于挂载XML视图)。
自定义view,通常如下:
MyView(Context c){
this(context, null);
}
MyView(Context c, AttributeSet attrs){
super(c, attrs);
//相关处理
}
在双参数构造函数中,您可以以任何姿势使用obtainStyledAttributes()。实现默认样式的一种快速方法是只向其提供defStyleRes;这样你就不需要经过在对接defstyleattr的繁琐。
无论如何,我希望这不仅有助于您了解视图构造函数,而且还可以在视图构建过程中检索属性!
坐到这里的都是真爱啊! 下车了,老司机!
网友评论