android view构造函数研究

上周遇到了SurfaceView的constructor的问题,周末决定略微细致地研究一下这个令人发指的玩意。

  SurfaceView是View的子类,与View一样有三个constructor:

1  public  void  CustomView( Context  context{}
2  public  void  CustomView( Context  context ,  AttributeSet  attrs{}
3  public  void  CustomView( Context  context ,  AttributeSet  attrs ,  int  defStyle{}

  为了方便,我们分别命名为C1,C2,C3。
  C1是最简单的一个,如果你只打算用code动态创建一个view而不使用布局文件xml inflate,那么实现C1就可以了。
  C2多了一个AttributeSet类型的参数,在通过布局文件xml创建一个view时,这个参数会将xml里设定的属性传递给构造函数。如果你采用xml inflate的方法却没有在code里实现C2,那么运行时就会报错。但是由于编译能顺利通过,对于我这样的菜鸟,这个错误有时不太容易被发现。
  关于C1和C2,google和度娘上都有很多文章介绍,我就不做赘述。

  扯淡的是C3。
  C3多了一个defStyle的int参数,关于这个参数doc里是这样描述的:

  The default style to apply to this view. If 0, no style will be applied (beyond what is included in the theme). This may either be an attribute resource, whose value will be retrieved from the current theme, or an explicit style resource.

  从字面上翻译,这个参数似乎是用来指定view的默认style的,如果是0,那么将不会应用任何默认(或者叫缺省)的style。另外这个参数可以是一个属性指定的style引用,也可以直接是一个显式的style资源。

  这仅仅是字面上翻译的结果,就已经不太好理解了。我琢磨了一下,大概有这么两个问题:

1. 这个C3什么时候会被调用?
  C1是代码创建view时,C2是xml创建view时,那么C3呢?既然defStyle是一个与指定style有关的参数,那么一个比较自然的猜想是当在代码比如xml里通过某种方式指定了view的style时,C3在该view被inflate时调用,并将style传入给defStyle。
  那么在xml里指定style有几种方式呢?大概有两种,一种是在直接在布局文件该view标签里使用
1  style="@style/customstyle"
来指定,另一种是采用指定theme的方式,在AndroidManifest.xml的application标签里使用
1  android:theme="@style/customstyle"
这两种方式都需要在res/values/styles.xml里定义customstyle:

1  <?xml version="1.0" encoding="utf-8"?>
2  <resources>
3         <style  name= "customstyle" >
4                 <item  name= "android:background" > @drawable/bg </item>
5          [... or other style code...]
6         </style>
7  </resources>

注:使用theme时标准的theme定义方式是把style放在themes.xml里而不是styles.xml,但实际上R.java在生成时无论是themes.xml和styles.xml里的style都是同质的,都存在于R.style下。

回到C3的问题上来,那么这两种指定style的方式会不会触发C3呢?很遗憾,经测试,不会。并且至今我没发现任何一种情况会自动地(隐式地)调用构造函数C3……不知道究竟有没有这种情况存在呢?

那么C3到底什么时候被调用呢?答案是当你显式调用它的时候。通常是在C1或者C2里,用

1  public  void  CustomView( Context  context ,  AttributeSet  attrs{
2         this( context ,  attrs ,  resid);
3  }

的方式将真正构造函数的实现转移到C3里,并由resid指定defStyle,作为默认style。比如android源码中button的实现:

  For example, a Button class's constructor would call this version of the super class constructor and supply R.attr.buttonStyle for defStyle; this allows the theme's button style to modify all of the base view attributes (in particular its background) as well as the Button class's attributes.

(摘自C3的doc)这里的R.attr.buttonStyle就是一个resid。于是引出了第二个问题。

2. defStyle接受什么样的值?
  你可能会说,doc上不是写着呢么?这个参数可以是一个属性指定的style引用,也可以直接是一个显式的style资源。
  那我们就试验一下看看。
  首先在res/styles.xml里定义一个style:

1  <?xml version="1.0" encoding="utf-8"?>
2  <resources>
3         <style  name= "purple" >
4                 <item  name= "android:background" > #FFFF00FF </item>
5         </style>
6  </resources>

  然后自定义一个View(或者SurfaceView也是可以的): CustomView.java

01  package  com . your . test;
02
03  public  class  CustomView  extends  View  {
04
05         //C1
06         public  CustomView( Context  context{
07                 super( context);
08         }
09
10         //C2
11         public  CustomView( Context  context ,  AttributeSet  attrs{
12                 this( context ,  attrs ,  0);
13         }
14
15         //C3
16         public  CustomView( Context  context ,  AttributeSet  attrs ,  int  defStyle{
17                 super( context ,  attrs ,  defStyle);
18         }
19  }

  之后是布局文件layout/main.xml:

1  <?xml version="1.0" encoding="utf-8"?>
2  <LinearLayout  xmlns:android= "http://schemas.android.com/apk/res/android"
3                             xmlns:myxmlns= "http://schemas.android.com/apk/res/com.your.test"
4                             android:orientation= "vertical"
5                             android:layout_width= "fill_parent"
6                             android:layout_height= "fill_parent"  >
7           <com.your.test.CustomView  android:layout_width= "100px"
8                                                               android:layout_height= "100px"  />
9  </LinearLayout>

  最后是main activity文件Test1.java:

1  package  com . your . test;
2
3  public  class  Test  extends  Activity  {
4         @Override
5         public  void  onCreate( Bundle  savedInstanceState{
6                 super . onCreate( savedInstanceState);
7                 setContentView( R . layout . main);
8         }
9  }

把该import的import了,运行应该能看到一个正常的黑色背景的view。
下面应用我们定义的style试试看:

1  <com.your.test.CustomView  android:layout_width= "100px"
2                                                     android:layout_height= "100px"
3                                                     style= "@style/purple"
4  />

view的背景变成了紫色,但如果你log一下就会发现,调用的还是C2。
在AndroidManifest.xml里用theme指定,结果也差不多(细节差别可自己体会,不赘述)。

  下面我们就来研究defStyle到底接受什么样的参数。
  首先把style和theme的引用都去掉,还原到黑色背景的view。这样在程序里R.style.purple就是这个style的显式引用(其实到现在我也不知道doc里说的explicit style resource是不是就是这个意思……)那么,理论上我们把R.style.purple当作defStyle传给C3,是不是就能做到设定view的默认背景为紫色呢?

1  public  CustomView( Context  context ,  AttributeSet  attrs{
2         this( context ,  attrs ,  R . style . purple);
3  }

如果你log一下,就会发现,C2确实执行了,甚至R.style.purple也成功传给C3里的defStyle了,但是,view的背景还是黑色。
  这是为什么呢?是doc不对还是我不对?
  这个先暂且放下不谈,我们先试试那另外一种方式,传入一个引用style资源的属性(类似R.attr.buttonStyle)。这先要创建一个res/values/attrs.xml的文件,这个文件用来定义某个view里可以出现的属性:

1  <?xml version="1.0" encoding="utf-8"?>
2  <resources>
3         <declare-styleable  name= "CustomView" >
4                 <attr  name= "ourstyle"  format= "reference"  />
5                 <attr  name= "atext"  format= "string"  />
6         </declare-styleable>
7  </resources>

现在我们为CustomView增加了两个可以出现的自定义属性,ourstyle和atext,前者就是我们打算用来引用一个style资源的属性,后者是一个没什么用的字符串属性,放在这里只是为了后面做测试。
现在我们就可以在程序里引用这个属性并把这个参数传给defStyle。

  当然,在这之前我们先要把purple这个style赋值给ourstyle。给一个view的属性赋值,就和给android:layout_width赋值一样,除了命名空间不同(layout/main.xml的LinearLayout标签里有命名空间的声明):

1  <com.your.test.CustomView  android:layout_width= "100px"
2                                                     android:layout_height= "100px"
3                                                     myxmlns:ourstyle= "@style/purple"
4                                                     myxmlns:atext= "test string"
5  />

也可以用指定theme的方法,在theme里给所有的CustomView都赋予一个相同的默认的ourstyle值,然后应用这个theme:

在styles.xml里另外定义一个style作为theme:

1  <style  name= "purpletheme" >
2         <item  name= "ourstyle" > @style/purple </item>
3  </style>

在AndroidManifest.xml的Application标签中应用theme:
1  android:theme="style/purpletheme"
这两种指定属性的方法不同,在程序里引用这个属性的方法也不同。theme指定的属性,可以直接用R.attr.ourstyle来引用,也可以用R.styleable.CustomView[R.styleable.CustomView_ourstyle]来引用,于是:

1  //C2
2  public  CustomView( Context  context ,  AttributeSet  attrs{
3         this( context ,  attrs ,  R . attr . ourstyle );
4  }

这样就成功地让defStyle生效了。

那么直接在标签里赋值的属性怎么引用呢?
直接在标签里赋值的属性,都会在xml inflate时通过AttributeSet这个参数传给C2,所以我们可以通过AttributeSet类提供的getAttributeResourceValu e方法来获取属性的值。但是很可惜的是,我们只能获取到属性的值,而无法获取包含这个值的属性的引用(getAttributeNameResource 方法返回的是和R.attr.ourstyle一样的值,但这时R.attr.ourstyle并未指向@style/purple),这些乱七八糟的方法的各种值之间具体差别可以参考以下代码的log结果,相信仔细揣摩不难明白其中奥妙:

01  //C2
02  public  CustomView( Context  context ,  AttributeSet  attrs{
03         this( context ,  attrs ,  attrs . getAttributeNameResource( 2));
04         String  a1  = (( Integer) R . attr . ourstyle ). toString();
05         String  a2  = (( Integer) R . styleable . CustomView_ourstyle ). toString();
06         String  a3  = (( Integer) R . styleable . CustomView [ R . styleable . CustomView_ourstyle ]). toString();
07         String  a4  = (( Integer) R . style . purple ). toString();
08         String  a5  = (( Integer) attrs . getAttributeNameResource( 2 )). toString();
09         String  a6  = (( Integer) attrs . getAttributeResourceValue( 2 , 0 )). toString();
10         String  a7  = (( Integer) R . attr . atext ). toString();
11         String  a8  = (( Integer) R . styleable . CustomView [ R . styleable . CustomView_atext ]). toString();
12         String  a9  =  attrs . getAttributeValue( 2);
13         String  a10  =  attrs . getAttributeValue( 3);
14  }

log一下a1-10,示例结果如下:

01  a1  =  2130771968
02  a2  =  0
03  a3  =  2130771968
04  a4  =  2131034112
05  a5  =  2130771968
06  a6  =  2131034112
07  a7  =  2130771969
08  a8  =  2130771969
09  a9  =  @2131034112
10  a10  =  test  string

凡是值相同的其实是一种意思,a1, a3, a5都指的是attrs.xml里属性的引用,这个引用id只有在theme里赋值才有效,直接在标签里赋值是无效的。而传这个id给defStyle就正符合doc里写的第一种情况。而a4, a6则直接代表了@style/purple的id,即doc里写的第二种情况:

android <wbr>view构造函数研究

但是,回到我们最初的问题,传R.style.purple,也就是2131034112这个id给defStyle是没有效果的,为什么呢?这个原因只能到android源码的view.java里去看个究竟了:

1  public  View( Context  context ,  AttributeSet  attrs ,  int  defStyle{
2         this( context);
3         TypedArray  a  =  context . obtainStyledAttributes( attrs ,  com . android . internal . R . styleable . View , defStyle ,  0);
4         [... other  code ...]

这就是view基类的构造函数C3,它在接受defStyle参数后利用context.obtainStyledAttributes这个方法来构造一个完整的属性数组,几个参数综合起来提供了从主题、样式里继承来的和直接在标签里定义的所有属性,具体可以看这个方法的doc:

public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
Since:  API Level 1

Return a StyledAttributes holding the attribute values in set that are listed in attrs. In addition, if the given AttributeSet specifies a style class (through the "style" attribute), that style will be applied on top of the base attributes it defines.

Be sure to call StyledAttributes.recycle() when you are done with the array.

When determining the final value of a particular attribute, there are four inputs that come into play:

  1. Any attribute values in the given AttributeSet.
  2. The style resource specified in the AttributeSet (named "style").
  3. The default style specified by defStyleAttr and defStyleRes
  4. The base values in this theme.

Each of these inputs is considered in-order, with the first listed taking precedence over the following ones. In other words, if in the AttributeSet you have supplied <Button textColor="#ff000000">, then the button's text will always be black, regardless of what is specified in any of the styles.

Parameters
set The base set of attribute values. May be null.
attrs The desired attributes to be retrieved.
defStyleAttr An attribute in the current theme that contains a reference to a style resource that supplies defaults values for the StyledAttributes. Can be 0 to not look for defaults.
defStyleRes A resource identifier of a style resource that supplies default values for the StyledAttributes, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.
Returns
  • Returns a TypedArray holding an array of the attribute values. Be sure to call TypedArray.recycle() when done with it.

于是我就纳闷了,显式的资源调用难道不是应该通过defStyleRes这个参数么?为什么这里直接就写成0了呢?这里写成0,那当然defStyle只能采取defStryleAttr的方式了。google了一下,还真在android的google code project里发现了一个developer提交的 Issue 12683提到了这种情况,不过也没人comment,不知道究竟是不是这样……

烂尾收工,慢走不送~
  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值