你真的了解Android View构造函数么

2017-04-10  本文已影响98人  f6876752a23f

译自: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的繁琐。

无论如何,我希望这不仅有助于您了解视图构造函数,而且还可以在视图构建过程中检索属性!

坐到这里的都是真爱啊! 下车了,老司机!

上一篇下一篇

猜你喜欢

热点阅读