Android自定义View构造函数详解 Posted 2023-02-21 changhaiSmile
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义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, attrs, defStyle);
@Override
protected void onDraw (Canvas canvas)
super .onDraw(canvas);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
网上有很多关于三个构造函数使用时机的说法,但是说法正确的却没有几家,这里正式的给大家科普一下:
在代码中直接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, AttributeSet attrs, int defStyleAttr)
super (context, attrs, 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, attrs, 0 , 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);
final XmlBlock.Parser parser = (XmlBlock.Parser)set;
AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
parser != null ? parser.mParseState : 0 , attrs, 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;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
这里就不做过多的源码讲解,而是把这四个参数的含义解释给大家:
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"
style ="@style/TestCustomView" />
</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