android view构造函数研究

摘要:
如果您只计划使用代码动态创建视图,而不是使用布局文件xmliniflate,则此参数会将xml中的属性集传递给构造函数。此参数似乎用于指定视图的默认样式,因此不会应用默认(或默认)样式。更自然的猜测是,当视图样式以某种方式在诸如xml之类的代码中指定时。
       上周遇到了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类提供的getAttributeResourceValue方法来获取属性的值。但是很可惜的是,我们只能获取到属性的值,而无法获取包含这个值的属性的引用(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
setThe base set of attribute values. May be null.
attrsThe desired attributes to be retrieved.
defStyleAttrAn 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.
defStyleResA 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,不知道究竟是不是这样……

免责声明:文章转载自《android view构造函数研究》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ngx_lua应用最佳实践JMeter学习(一)工具简单介绍下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

TypeScript 中slice(-1)是什么意思?

slice(start,end)方法提取字符串的一部分并返回一个新字符串。 使用方法 一般来说该方法有两个参数,使用方法如下:slice(start,end)    start表示要提取的片段的起始下标,end表示提取片段最后一个字符的后一个字符的下标; 1.两个参数都为正数 var str="Hello happy world!" document.wr...

WSL使用小结:从ArchLinux到Manjaro

 1.前言   上一篇介绍了Windows 10下配置WSL环境,通过ALWSL脚本替换为ArchLinux的过程。这一篇介绍根据ArchLinux官网的说明,在WSL下安装ArchLinux,并切换到发行版Manjaro的过程。   先上截图:X Server使用的是MobaXterm,其中:Windows 10任务栏以上部分是Manjaro界面,四个窗...

excel文件使用navicat工具导入mysql的方法

1、在excel文件的sheet上,第1行下面插入一行,对应DB里面的字段名称,方便后面导入时做字段匹配: 2、使用Navicat ,打开工具,选择表所在的数据库,然后点击数据库名字,右键Tables,出来下拉菜单选择import wizard(中文版:导入向导).弹出一个选择界面,选择excel file文件 3、点击next(下一步),选择对应...

PowerShell初探

Windows PowerShell是一种命令行外壳程序和脚本环境,它内置在每个受支持的Windows版本中(Windows 7/Windows 2008 R2和更高版本),使命令行用户和脚本编写者可以利用.NET Framework的强大功能。一旦攻击者可以在一台计算机上运行代码,他们就会下载Powershell脚本文件(.ps1)到磁盘中执行,甚至无需...

Parquet与ORC:高性能列式存储格式(收藏)

背景 随着大数据时代的到来,越来越多的数据流向了Hadoop生态圈,同时对于能够快速的从TB甚至PB级别的数据中获取有价值的数据对于一个产品和公司来说更加重要,在Hadoop生态圈的快速发展过程中,涌现了一批开源的数据分析引擎,例如Hive、Spark SQL、Impala、Presto等,同时也产生了多个高性能的列式存储格式,例如RCFile、ORC、P...

隐藏Apache版本号

  为什么要隐藏版本号? 一般情况下,软件的漏洞信息和特定版本是相关的,因此,软件的版本号对攻击者来说是很有价值的。 如何隐藏? 要隐藏Apache版本号其实方法很简单在,只要在httpd.conf中对ServerTokens Prod与ServerSignature Off进行设置即可。 在默认情况下,系统会把Apache版本模块都显示出来(http返回...