Android自定义View构造函数详解

Android自定义View构造函数详解,第1张

概述初始CustomView的构造函数之前写过一篇实现圆形进度条的博客(自定义圆形进度条),通常我们在实现CustomView的时候,都会先继承View并实现View的三个构造函数,例如:

初始Custom VIEw的构造函数

之前写过一篇实现圆形进度条的博客(自定义圆形进度条),通常我们在实现Custom VIEw的时候,都会先继承VIEw并实现VIEw的三个构造函数,例如:

import androID.content.Context;import androID.graphics.Canvas;import androID.util.AttributeSet;import androID.vIEw.VIEw;public class MyCustomVIEw extends VIEw { /**  * 第一个构造函数  */ public MyCustomVIEw(Context context) {  this(context,null); } /**  * 第二个构造函数  */ public MyCustomVIEw(Context context,AttributeSet attrs) {  this(context,attrs,0); } /**  * 第三个构造函数  */ public MyCustomVIEw(Context context,AttributeSet attrs,int defStyle) {  super(context,defStyle);  // Todo:获取自定义属性 } @OverrIDe protected voID onDraw(Canvas canvas) {  super.onDraw(canvas); }}

网上有很多关于三个构造函数使用时机的说法,但是说法正确的却没有几家,这里正式的给大家科普一下:

在代码中直接new一个Custom VIEw实例的时候,会调用第一个构造函数.这个没有任何争议.
在xml布局文件中调用Custom VIEw的时候,会调用第二个构造函数.这个也没有争议.
在xml布局文件中调用Custom VIEw,并且Custom VIEw标签中还有自定义属性时,这里调用的还是第二个构造函数.
也就是说,系统默认只会调用Custom VIEw的前两个构造函数,至于第三个构造函数的调用,通常是我们自己在构造函数中主动调用的(例如,在第二个构造函数中调用第三个构造函数).

至于自定义属性的获取,通常是在构造函数中通过obtainStyledAttributes函数实现的.这里先介绍一下如何生成Custom VIEw的自定义属性.

生成Custom VIEw的自定义属性

Custom VIEw添加自定义属性主要是通过declare-styleable标签为其配置自定义属性,具体做法是: 在res/values/目录下增加一个resources xml文件,示例如下(res/values/attrs_my_custom_vIEw.xml):

<resources> <declare-styleable name="MyCustomVIEw">  <attr name="custom_attr1" format="string" />  <attr name="custom_attr2" format="string" />  <attr name="custom_attr3" format="string" />  <attr name="custom_attr4" format="string" /> </declare-styleable> <attr name="custom_attr5" format="string" /></resources>

在上述xml文件中,我们声明了一个自定义属性集MyCustomVIEw,其中包含了custom_attr1,custom_att2,custom_attr3,custom_attr4四个属性.同时,我们还声明了一个独立的属性custom_attr5.

所有resources文件中声明的属性都会在R.attr类中生成对应的成员变量:

public final class R { public static final class attr {  public static final int custom_attr1=0x7f010038;  public static final int custom_attr2=0x7f010039;  public static final int custom_attr3=0x7f01003a;  public static final int custom_attr4=0x7f01003b;  public static final int custom_attr5=0x7f010000; }}

但是声明在标签中的属性,系统还会在R.styleable类中生成相关的成员变量:

public static final class styleable {  public static final int[] MyCustomVIEw = {   0x7f010038,0x7f010039,0x7f01003a,0x7f01003b  };  public static final int MyCustomVIEw_custom_attr1 = 0;  public static final int MyCustomVIEw_custom_attr2 = 1;  public static final int MyCustomVIEw_custom_attr3 = 2;  public static final int MyCustomVIEw_custom_attr4 = 3;}

可以看出,R.styleable.MyCustomVIEw是一个数组,其中的元素值恰好就是R.attr.custom_attr1~R.attr.custom_attr4的值.而下面的MyCustomVIEw_custom_attr1~MyCustomVIEw_custom_attr4正好就是其对应的索引.

知道了这些之后,我们就可以来学习一下,如何在Custom VIEw的构造函数中获取自定义属性的值了.

在Custom VIEw的构造函数中获取自定义属性

在第三个构造函数中获取自定义属性的代码如下:

public MyCustomVIEw(Context context,int defStyleAttr) { super(context,defStyleAttr); TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.MyCustomVIEw); String attr1 = ta.getString(R.styleable.MyCustomVIEw_custom_attr1); String attr2 = ta.getString(R.styleable.MyCustomVIEw_custom_attr2); String attr3 = ta.getString(R.styleable.MyCustomVIEw_custom_attr3); String attr4 = ta.getString(R.styleable.MyCustomVIEw_custom_attr4); Log.e("customvIEw","attr1=" + attr1); Log.e("customvIEw","attr2=" + attr2); Log.e("customvIEw","attr3=" + attr3); Log.e("customvIEw","attr4=" + attr4); ta.recycle(); }

关于自定义属性的获取,我们主要是调用了context.obtainStyledAttributes这个函数,相信这个函数大家自定义view的时候都用的很熟练了.我们来看一下这个函数的源码实现:

public final TypedArray obtainStyledAttributes(AttributeSet set,@StyleableRes int[] attrs) { return gettheme().obtainStyledAttributes(set,0);}

通过对源码的追踪,我们发现context的两个参数的obtainStyledAttributes方法最终是调用了theme的4个参数的obtainStyledAttributes方法.我们来看一下这个函数的源码实现:

public TypedArray obtainStyledAttributes(AttributeSet set,@StyleableRes int[] attrs,@AttrRes int defStyleAttr,@StyleRes int defStyleRes) { final int len = attrs.length; final TypedArray array = TypedArray.obtain(Resources.this,len); // XXX note that for Now we only work with compiled XML files. // To support generic XML files we will need to manually parse // out the attributes from the XML file (applying type information // contained in the resources and such). final XmlBlock.Parser parser = (XmlBlock.Parser)set; AssetManager.applyStyle(mtheme,defStyleAttr,defStyleRes,parser != null ? parser.mParseState : 0,array.mData,array.mIndices); array.mtheme = this; array.mXml = parser; if (false) {  int[] data = array.mData;  System.out.println("Attributes:");  String s = " Attrs:";  int i;  for (i=0; i<set.getAttributeCount(); i++) {   s = s + " " + set.getAttributename(i);   int ID = set.getAttributenameResource(i);   if (ID != 0) {    s = s + "(0x" + Integer.toHexString(ID) + ")";   }   s = s + "=" + set.getAttributeValue(i);  }  System.out.println(s);  s = " Found:";  TypedValue value = new TypedValue();  for (i=0; i<attrs.length; i++) {   int d = i*AssetManager.STYLE_NUM_ENTRIES;   value.type = data[d+AssetManager.STYLE_TYPE];   value.data = data[d+AssetManager.STYLE_DATA];   value.assetcookie = data[d+AssetManager.STYLE_ASSET_cookie];   value.resourceID = data[d+AssetManager.STYLE_RESOURCE_ID];   s = s + " 0x" + Integer.toHexString(attrs[i])    + "=" + value;  }  System.out.println(s); } return array;}

这里就不做过多的源码讲解,而是把这四个参数的含义解释给大家:

AttributeSet set: 属性值的集合.

int[] attrs: 我们自定义属性集合在R类中生成的int型数组.这个数组中包含了自定义属性的资源ID.
int defStyleAttr: 这是当前theme中的包含的一个指向style的引用.当我们没有给自定义view设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值.传入0表示不像该defStyleAttr中查找默认值.
int defStyleRes: 这个也是一个指向Style的资源ID,但是仅在defStyleAttr为0或者defStyleAttr不为0但theme中没有为defStyleAttr属性赋值时起作用.

由于一个属性可以在很多地方对其进行赋值,包括: XML布局文件中、decalare-styleable、theme中等,它们之间是有优先级次序的,按照优先级从高到低排序如下:

属性赋值优先级次序表:

在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义view所在的Activity的theme中指定style引用 > 构造函数中defStyleRes指定的默认值

为了让大家有更清楚更直观的了解,再接下来设置自定义属性的章节中,我将对custom_attr1~4这4个属性分别在上述四个地方进行定义,然后在Custom VIEw的构造函数中获取它的值,从而看一下,优先级顺序是否和我们预期的一样.

设置自定义属性值

以下的第几个参数均是针对Resources.theme类的obtainStyledAttributes四参数构造方法来说明的.

第二个参数――在布局xml文件中为属性赋值

在设置自定义属性之前,我们首先要在主Activity的布局文件中调用我们的Custom VIEw,并且为其设置特定的属性.

主布局文件内容如下:

<FrameLayout xmlns:androID="http://schemas.androID.com/apk/res/androID" xmlns:custom="http://schemas.androID.com/apk/res-auto" androID:ID="@+ID/container" androID:layout_wIDth="match_parent" androID:layout_height="match_parent"> <com.kevintan.eventbussample.vIEw.MyCustomVIEw  androID:ID="@+ID/ID_custom_vIEw"  androID:layout_wIDth="400dp"  androID:layout_height="400dp"  custom:custom_attr1="attr1_xml"  /></FrameLayout>

示例结果:

05-28 17:19:56.542 23575-23575/? E/customvIEw: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customvIEw: attr2=null
05-28 17:19:56.542 23575-23575/? E/customvIEw: attr3=null
05-28 17:19:56.542 23575-23575/? E/customvIEw: attr4=null

注意:

在给自定义属性赋值时,首先需要增加自定义属性的命名空间,例如: xmlns:custom=”http://schemas.AndroID.com/apk/res-auto”,AndroID Studio推荐使用res-auto,在Eclipse中需要使用Custom VIEw所在的包名: xmlns:cv=”http://schemas.androID.com/apk/com.kevintan.eventbussample.vIEw”
这里,在布局文件中我们为custom_attr1赋值为: attr1_xml.自定义view中获取该属性值对应了gettheme().obtainStyledAttributes方法中的第二个参数@StyleableRes int[] attrs

第二个参数――在style中为属性赋值

其次,自定义属性还可以在Style中进行赋值.

首先,我们在xml布局文件中还为MyCustomVIEw增加一个自定义的style,style代码如下:

<style name="TestCustomVIEw"> <item name="custom_attr1">attr1_style</item> <item name="custom_attr2">attr2_style</item></style>

然后,我们修改布局文件,增加style字段:

<FrameLayout xmlns:androID="http://schemas.androID.com/apk/res/androID" xmlns:custom="http://schemas.androID.com/apk/res-auto" androID:ID="@+ID/container" androID:layout_wIDth="match_parent" androID:layout_height="match_parent"> <com.kevintan.eventbussample.vIEw.MyCustomVIEw  androID:ID="@+ID/ID_custom_vIEw"  androID:layout_wIDth="400dp"  androID:layout_height="400dp"  custom:custom_attr1="attr1_xml"  /></FrameLayout>

示例结果:

05-28 17:19:56.542 23575-23575/? E/customvIEw: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customvIEw: attr2=attr2_style
05-28 17:19:56.542 23575-23575/? E/customvIEw: attr3=null
05-28 17:19:56.542 23575-23575/? E/customvIEw: attr4=null

这里我们再次对custom_attr1属性进行了赋值,同时我们对custom_attr2也进行了赋值.

小提示:

聪明的同学肯定都猜到我这样赋值的作用了,但是还是要简述一下:
对于custom_attr1,我们在xml布局文件、style、defStyle和theme中均进行赋值,那最终得到的结果必然能证实谁的优先级最高.
对于custom_attr2,我们在style、defStyle和theme中进行赋值,通过得到的结果我们能知道谁的优先级第二高.
对于custom_attr3和custom_attr4的赋值情况我就不多解释了,我相信大家都懂得!!
同时,还需要大家注意的是,只要是layout布局文件中,无论是通过namespace直接为属性赋值,还是通过style为属性赋值,在构造函数获取时都对应了gettheme().obtainStyledAttributes方法中的第二个参数@StyleableRes int[] attrs

第三个参数defStyleAttr

这个参数的意思是:

原文: An attribute in the current theme that contains areference to a style resource that supplIEs defaults values for the TypedArray. Can be 0 to not look for defaults.
翻译: 这是当前theme中的包含的一个指向style的引用.当我们没有给自定义view设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值.传入0表示不向该defStyleAttr中查找默认值.
为了测试该参数的作用和优先级,我们需要进行如下 *** 作.

首先,我们先声明一个refrence格式的属性,用于表示style的引用.声明在之前的res/values/attrs_my_custom_vIEw.xml文件里即可:

<attr name="MyCustomVIEwDefStyleAttr" format="reference"/>

然后,需要到AndroIDManifest.xml中查看包含该自定义view的Activity所使用的主题是:

<activity> androID:name="com.kevintan.eventbussample.MainActivity" androID:theme="@style/Apptheme" androID:label="@string/app_name" > <intent-filter>  <action androID:name="androID.intent.action.MAIN" />  <category androID:name="androID.intent.category.LAUNCHER" /> </intent-filter></activity>

最后,在style.xml中的Apptheme主题下增加MyCustomVIEwDefStyleAttr的引用实现.

<style name="Apptheme" parent="androID:theme.Holo.light.DarkActionbar"> <!-- Customize your theme here. --> <item name="MyCustomVIEwDefStyleAttr">@style/MyCustomVIEwDefStyleAttrImpl</item></style><style name="MyCustomVIEwDefStyleAttrImpl"> <item name="custom_attr1">attr1_defStyleAttr</item> <item name="custom_attr2">attr2_defStyleAttr</item> <item name="custom_attr3">attr3_defStyleAttr</item></style>

代码验证,记住如果要使用obtainStyledAttributes方法的第三个参数,就需要在第三个构造函数中显示的调用gettheme()的obtainStyledAttributes方法.

public MyCustomVIEw(Context context,AttributeSet attrs) { // 为defStyleAttr进行赋值 this(context,R.attr.MyCustomVIEwDefStyleAttr);}public MyCustomVIEw(Context context,defStyleAttr); TypedArray ta = context.gettheme().obtainStyledAttributes(attrs,R.styleable.MyCustomVIEw,0); String attr1 = ta.getString(R.styleable.MyCustomVIEw_custom_attr1); String attr2 = ta.getString(R.styleable.MyCustomVIEw_custom_attr2); String attr3 = ta.getString(R.styleable.MyCustomVIEw_custom_attr3); String attr4 = ta.getString(R.styleable.MyCustomVIEw_custom_attr4); Log.e("customvIEw","attr4=" + attr4); ta.recycle();}

代码中,有两点需要大家注意:

我在第二个构造参数中已经对defStyleAttr进行了赋值,第三个构造参数直接使用传入参数即可.
第三个构造参数中,我使用了gettheme()的obtainStyledAttributes方法来代替context的2个参数的obtainStyledAttributes构造方法.
示例结果:

05-28 17:19:56.542 23575-23575/? E/customvIEw: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customvIEw: attr2=attr2_style
05-28 17:19:56.542 23575-23575/? E/customvIEw: attr3=attr3_defStyleAttr
05-28 17:19:56.542 23575-23575/? E/customvIEw: attr4=null

从结果可以看出,在主题中指定style引用的优先级是低于在xml中直接赋值和使用style字段的.

同时,我们还需要了解一点:
在AndroID系统中的控件,很多都在构造参数中使用了第三个参数,例如button.这样做的好处是: 当我们切换不同的主题时,button的样式也能随之进行改变.

第四个参数――通过defStyleRes为属性赋值

这个参数的意思是:

原文: A resource IDentifIEr of a style resource that supplIEs default values for the TypedArray,used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.
翻译: 这是一个指向Style的资源ID,但是仅在defStyleAttr为0或者defStyleAttr不为0但theme中没有为defStyleAttr属性赋值时起作用.
通过翻译,我们可以明确两点:

1.defStyleRes: 指向一个style引用.
2.defStyleRes的优先级低于defStyleAttr.
为了验证,我们先在theme.xml文件中定义一个style:

<style name="MyCustomVIEwDefStyleRes"> <item name="custom_attr1">attr1_defStyleRes</item> <item name="custom_attr2">attr2_defStyleRes</item> <item name="custom_attr3">attr3_defStyleRes</item> <item name="custom_attr4">attr4_defStyleRes</item></style>

然后,我们在自定义view的第三个构造函数中的obtainStyledAttributes函数中进行赋值,具体方法如下:

public MyCustomVIEw(Context context,AttributeSet attrs) { this(context,defStyleAttr); // 为defStyleRes进行赋值 TypedArray ta = context.gettheme().obtainStyledAttributes(attrs,R.style.MyCustomVIEwDefStyleRes); String attr1 = ta.getString(R.styleable.MyCustomVIEw_custom_attr1); String attr2 = ta.getString(R.styleable.MyCustomVIEw_custom_attr2); String attr3 = ta.getString(R.styleable.MyCustomVIEw_custom_attr3); String attr4 = ta.getString(R.styleable.MyCustomVIEw_custom_attr4); Log.e("customvIEw","attr4=" + attr4); ta.recycle();}

测试结果:

05-28 17:44:09.282 3137-3137/? E/customvIEw: attr1=attr1_xml
05-28 17:44:09.282 3137-3137/? E/customvIEw: attr2=attr2_style
05-28 17:44:09.282 3137-3137/? E/customvIEw: attr3=attr3_defStyleAttr
05-28 17:44:09.282 3137-3137/? E/customvIEw: attr4=null

重点:
如果大家认真的看实验结果,肯定会被上面的结果感到奇怪,明明指定了defStyleRes,为什么attr4的值还是null?
是因为之前讲过defStyleRes的使用优先级:只有当defStyleAttr为0或者当前theme中没有给defStyleAttr属性赋值时才起作用.

所以,这里我们需要修改构造函数,将defStyleAttr设置为0.

public MyCustomVIEw(Context context,AttributeSet attrs) { // 为了验证defStyleRes的作用,将defStyleAttr设置为0 this(context,0);}public MyCustomVIEw(Context context,"attr4=" + attr4); ta.recycle();}

最终结果:

05-28 17:49:03.707 5772-5772/? E/customvIEw: attr1=attr1_xml
05-28 17:49:03.707 5772-5772/? E/customvIEw: attr2=attr2_style
05-28 17:49:03.707 5772-5772/? E/customvIEw: attr3=attr3_defStyleRes
05-28 17:49:03.707 5772-5772/? E/customvIEw: attr4=attr4_defStyleRes

后记

在文章结尾,我们再次总结一下自定义属性的属性赋值优先级:

在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义view所在的Activity的theme中指定style引用 > 构造函数中defStyleRes指定的默认值.

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

总结

以上是内存溢出为你收集整理的Android自定义View构造函数详解全部内容,希望文章能够帮你解决Android自定义View构造函数详解所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1147986.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存