View的构造函数分析
在自定义View的时候,我们在继承了View之后,需要重写4种构造函数,那么这四种构造函数是什么意义呢?
- 构造函数里的参数分别是什么意思
- 其被调用时机是什么时候
构造函数中的参数
看View源码的话,可以知道4个构造函数之间是互调用的,参数少的调用参数多的。因此看参数最多的那个构造函数即可知目前共有4个参数,这四个参数中,除了Context,其余三个参数都是用来为View指定初始化的参数的,这些参数来自于xml中指定的属性,包括layout、style和theme。
super(context, attrs, defStyleAttr, defStyleRes);
}
- context。类型为Context,这个就不用多说了,
- attrs。一个属性集合。保存了在xml中定义的属性。
- defStyleAttr。是当前Theme中的一个属性,指向style的一个引用,如果在layout和style中都没有指定属性的情况下,会从Theme中这个attribute指向的style中查找相应的属性值。如果这个值传入为0
- defStyleRes。是指向style的一个资源id,仅当defStyleAttr为0或者defStyleAttr不为0。但是Theme中没有为defStyleAttr属性赋值的情况下起作用。
Attributes
平时在xml中使用view的时候,会给view指定属性值,比如下面这样。
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon"
/>
那么这些属性是从哪来的呢。有过自定义view的经验的话都会知道,可以在attrs文件中,通过declare-styleable来设置属性,系统view也不例外。
<declare-styleable name="ImageView">
<attr name="src" format="reference|color" />
</declare-styleable>
编译之后,生成一些资源id。R.styleable.[name]以及每一个属性R.styleable.[name]_[attribute]。
- R.styleable.[name]是一个数组,里面保存了属性的资源id。可以利用这个数组,从AttributeSet中获取相关属性的值的集合。
- R.styleable.[name]_[attribute],这个值保存了该属性在R.styleable.[name]这个数组中的位置,可以理解为一个索引,从前面取出的相关属性的值的集合中取出对应的资源值。
obtainStyledAtributes
在自定义View中,会经常使用这个方法来获取xml文件中指定的属性值,这个方法有4个参数
public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
AttributeSet中保存了在xml中设置的资源及其值。
attrs是一个数组,即前面说的R.styleable.[name],里面保存了我们属性的资源ID。这个数组用于从属性值的数组中取出每个属性对应的属性值。利用这两个参数,可以取出xml中设置的属性值。
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();
}
给View设置属性默认值
很多时候,View在初始化的时候,属性需要有一个默认值。如何指定这个默认值呢。当然,最简单的是在View内部取属性值的时候,在代码中为其指定默认值。如果这个默认值需要在不同的Theme中有不同的值怎么办?难道要在View中判断Theme然后来指定不同的默认值么?两个Theme还好,多个Theme的话,这样很不利于扩展。
怎么解决这个问题呢?这时候就轮到defStyleAttr发挥了。前面说了,defStyleAttr是当前Theme中的一个属性,指向style的一个引用。obtainStyledAtributes方法的第三个参数就是defStyleAttr,这时候应该能大概理解defStyleAttr这个参数是做什么的了。
比方说,有三个不同的Theme,ThemeA,ThemeB,ThemeC,在这三个Theme 下,有一个CustomView需要有三种不同的展示样式,怎么办呢。
- 在styles.xml中定义一个BaseTheme,作为其余Theme的基类
<style name="BaseTheme" parent="AppTheme">
</style>
- 在attrs.xml中为该Theme中定义一个属性:customStyle
<declare-styleable name="BaseTheme">
<attr name="customStyle" format="reference" />
</declare-styleable>
- 为这个自定义View设置三个不同的customStyleA,customStyleB,customStyleC。
<style name="customStyleA">
<item name="customTextColor">@android:color/black</item>
</style>
<style name="customStyleB">
<item name="customTextColor">@android:color/holo_red_light</item>
</style>
<style name="customStyleC">
<item name="customTextColor">@android:color/holo_blue_light</item>
</style>
- 定义三个Theme:ThemeA,ThemeB和ThemeC,每一个Theme都会引用一个不同的style
<style name="ThemeA" parent="BaseTheme" >
<item name="customStyle">@style/customStyleA</item>
</style>
<style name="ThemeB" parent="BaseTheme" >
<item name="customStyle">@style/customStyleB</item>
</style>
<style name="ThemeC" parent="BaseTheme" >
<item name="customStyle">@style/customStyleC</item>
</style>
- 在代码中获取属性值,注意,这里调用obtainStyledAttributes方法的时候,第三个参数,传的是自定义的customStyle的属性id。根据前面的步骤,这个属性是Theme的一个attribute,并且在不同的Theme下指向了不同的style。这样就可以在不同的Theme下获取不同的属性值。
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView, R.attr.customStyle, 0);
int color = array.getColor(R.styleable.CustomTextView_customTextColor, Color.BLACK);
setTextColor(color);
array.recycle();
通过前面的分析,defStyleAttr的作用应该讲清楚了,那么defStyleRes是做什么用的呢?defStyleRes相对defStyleAttr更简单,实际上就是一个style的资源id。如果需求比较简单,属性不需要和Theme绑定,或者有些Theme不想指定style。那么可以自己定义一个style。
<style name="customStyle">
<item name="customTextColor">@android:color/black</item>
</style>
然后调用obtainStyledAttributes方法,注意第三个参数传的是0,第四个参数传的是自定义style的id。
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView, 0, R.style.customStyle);
int color = array.getColor(R.styleable.CustomTextView_customTextColor, Color.BLACK);
setTextColor(color);
array.recycle();
属性值设置的优先级
通过前面的分析,可以知道有如下几种方式可以设置属性值。
在XML文件中设置属性值
- 在layout中指定参数并赋值
- 在style中指定参数并赋值,然后将该style作为layout的参数赋值
- 在theme中指定参数并赋值,然后将该theme设置给application或者activity
在代码中设置属性值
- 通过外部调用相关方法设置属性值
- 通过指定defStyleAttr来设置属性值
- 通过指定defStyleRes来设置属性值
那么这些方式之间肯定有一个优先级顺序,否则同时使用的话肯定会出现混乱。这个顺序如下:
- layout定义 > style中定义 > defStyleAttr定义 > defStyleRes定义 > theme中定义
- 如果指定了defStyleAttr,而且该属性是存在的,那么defStyleRes将不起作用,即使defStyleRes中定义了defStyleAttr中不存在的参数,也不会生效。
如果感兴趣的话,可以自己去实验下。
构造函数调用时机
通过前面的分析,构造函数的调用时机也就好分析了。
- 单参数构造函数(1)。当我们需要在代码中实例化一个View的时候,我们可以使用该构造函数,只需要传入一个Context,其余的属性通过方法设置。
- 两个参数构造函数(2)。我们在XML中写的View,最后转换成Java中可以引用的对象这个过程,都是通过反射的方式来实现的。具体可以参考 XML文件如何被加载这篇文章。反射调用的构造函数,即是两个参数的构造函数。
- 三个参数构造函数(3)。
- 四个参数构造函数(4)。这两个构造函数,系统是不会调用的,而是由View显示调用。比如,在2中调用3,并传入defStyleAttr。再比如,在3中调用4,并传入defStyleRes。
public class CustomTextView extends TextView {
public CustomTextView(Context context) {
this(context, null);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, R.attr.customStyle, 0);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView, defStyleAttr, R.style.customStyle);
int color = array.getColor(R.styleable.CustomTextView_customTextColor, Color.BLACK);
setTextColor(color);
array.recycle();
}
}
网友评论