构造函数的调用时机
在自定义 View 的过程当中,不可避免的需要接触到 View 的构造函数,目前 View 具有四个构造函数,分别如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public View (Context context) { /...../ } public View (Context context, @Nullable AttributeSet attrs) { this (context, attrs, 0 ); } public View (Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this (context, attrs, defStyleAttr, 0 ); } public View (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { this (context); /...../ }
defStyleAttr 当前主题中的一个属性,其中包含对为视图提供默认值的样式资源的引用。 可以为0,不寻找默认值。
defStyleRes 为视图提供默认值的样式资源的资源标识符,仅在 defStyleAttr 为0或在主题中找不到时使用。可以为0,不寻找默认值。
第一个构造函数源码的解释如下: > Simple constructor to use when creating a view from code.
也就是说当在代码中创建 View 的时候会调用第一个构造函数。
第二个构造函数的源码解释如下: > Constructor that is called when inflating a view from XML. This is called when a view is being constructed from an XML file, supplying attributes that were specified in the XML file. This version uses a default style of 0, so the only attribute values applied are those in the Context's Theme and the given AttributeSet.
也就是说当在 XML 文件中插入 View 的时候会调用第二个构造函数,并且会应用 Context 的主题以及在 XML 中给定的属性值。
第三个和第四个构造函数的源码解释分别如下: > Perform inflation from XML and apply a class-specific base style from a theme attribute. > > >This constructor of View allows subclasses to use their own base style when they are inflating.
For example, a Button class's constructor would call this version of the super class constructor and supply R.attr.buttonStyle
for defStyleAttr ; this (in particular its background) as well as the Button class's attributes.
Perform inflation from XML and apply a class-specific base style from a theme attribute or style resource.
This constructor of View allows subclasses to use their own base style when they are inflating.
也就是说两个构造函数共同的作用是允许 View 应用自己的基础 style,那么当我们需要为 View 设置 style 的时候就可以选择去调用这两个构造函数当中的一个,调用方式一般如下:
1 2 3 4 5 6 7 public Button (Context context, AttributeSet attrs) { this (context, attrs, com.android.internal.R.attr.buttonStyle); } public Button (Context context, AttributeSet attrs, int defStyleAttr) { this (context, attrs, defStyleAttr, 0 ); }
defStyleAttr 和 defStyleRes 的联系
接下来的问题是这两者的区别在哪里?由于这两者都用于设置 style,而 style 里面是属性的集合,所以这里需要先了解 View 设置属性的方式,一般来说有如下五种方式: 1. XML 中直接声明相关属性值 2. XML 中 引入 style 3. defStyleAttr 4. defStyleRes 5. theme中直接指定
既然有五种方式,那么当同时应用的时候就涉及到了优先级的问题。为了了解优先级的问题,首先我们需要在 attrs.xml 文件中写入如下代码:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8" ?> <resources > <declare-styleable name ="DefAttrs" > <attr name ="attr_1" format ="string" /> <attr name ="attr_2" format ="string" /> <attr name ="attr_3" format ="string" /> <attr name ="attr_4" format ="string" /> </declare-styleable > <attr name ="defStyleAttr" format ="reference" /> </resources >
这里定义了四个格式为 string 的属性用于测试,定义了一个名为 defStyleAttr 引用。
通过 declare-styleable 可以为每个属性在 R 文件里自动生成一个 int[],这样就可以很方便的用 R.styleable.* 来进行使用,当然定义的属性也可以不放在 declare-styleable 中,但是使用的时候就需要通过如下代码:
1 2 int [] attrs = {R.attr.attr_1,R.attr_2};TypedArray typedArray = context.obtainStyledAttributes(set,attrs);
在创建了自定义的属性之后,还需要创建一些 style,在 style.xml 文件中写入如下代码:
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 <resources > <style name ="AppTheme" parent ="Theme.AppCompat.Light.DarkActionBar" > <item name ="colorPrimary" > @color/colorPrimary</item > <item name ="colorPrimaryDark" > @color/colorPrimaryDark</item > <item name ="colorAccent" > @color/colorAccent</item > <item name ="attr_1" > theme_one</item > <item name ="attr_2" > theme_two</item > <item name ="attr_3" > theme_three</item > <item name ="attr_4" > theme_four</item > <item name ="defStyleAttr" > @style/def_style_attr</item > </style > <style name ="xml_style" > <item name ="attr_1" > xml_style_one</item > <item name ="attr_2" > xml_style_two</item > </style > <style name ="def_style_attr" > <item name ="attr_1" > def_style_one</item > <item name ="attr_2" > def_style_two</item > <item name ="attr_3" > def_style_three</item > </style > <style name ="def_style_res" > <item name ="attr_1" > def_style_res_one</item > <item name ="attr_2" > def_style_res_two</item > <item name ="attr_3" > def_style_res_three</item > <item name ="attr_4" > def_style_res_four</item > </style > </resources >
上述代码添加了四种 style,对应为 View 设置属性的方式中的后四种,在添加了 style 之后,我们还需要应用 style,所以需要先创建一个如下的自定义 View:
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 public class MyView extends TextView { public MyView (Context context) { super (context); } public MyView (Context context, @Nullable AttributeSet attrs) { this (context, attrs, R.attr.defStyleAttr); } public MyView (Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this (context, attrs, defStyleAttr,R.attr.defStyleAttr); parse(context, attrs, defStyleAttr); } @TargetApi(VERSION_CODES.LOLLIPOP) public MyView (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super (context, attrs, defStyleAttr, defStyleRes); } private void parse (Context context, AttributeSet attrs, int defStyleAttr) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DefAttrs, defStyleAttr, R.style.def_style_res); String one = typedArray.getString(R.styleable.DefAttrs_attr_1); String two = typedArray.getString(R.styleable.DefAttrs_attr_2); String three = typedArray.getString(R.styleable.DefAttrs_attr_3); String four = typedArray.getString(R.styleable.DefAttrs_attr_4); log("one = " + one); log("two = " + two); log("three = " + three); log("four = " + four); typedArray.recycle(); } private void log (String msg) { Log.i("MyView" , msg); } }
第二个构造函数中显式调用了第三个构造函数,并设置 defStyleAttr 参数值为 R.attr.defStyleAttr。
parse() 获取四个属性值并进行打印。
创建完自定义 View 之后,现在需要在 XML 文件中添加自定义的View:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?xml version="1.0" encoding="utf-8" ?> <android.support.constraint.ConstraintLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:tools ="http://schemas.android.com/tools" xmlns:test ="http://schemas.android.com/apk/res-auto" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context ="com.rookieyang.test.MainActivity" > <com.rookieyang.test.MyView test:attr_1 ="xml_attr_attr1" style ="@style/xml_style" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> </android.support.constraint.ConstraintLayout >
test:attr_1="xml_attr_attr1" XML 中直接指定 attr_1 属性值。
style="@style/xml_style " XML 中引入 style。
运行结果
在完成上述步骤之后就可以运行,运行之后的输出如下: 上述代码中,XML 中直接声明 attr_1 属性值为 xml_attr_attr1,XML 中 引入的 style 中给 attr_1,attr_2 赋值为 xml_style_one,xml_style_two,defStyleAttr 给 attr_1,attr_2,attr_3 赋值为 def_style_one,def_style_two,def_style_three,defStyleRes 和 Theme 也分别为四个属性进行了赋值,而根据输出的结果在优先级上我们很容易得到以下结论: > XML 中直接声明相关属性值 > XML 中 引入 style > defStyleAttr > theme中直接指定 > > defStyleAttr > defStyleRes
从第一部分可以知道由于 defStyleAttr 的存在,defStyleRes 没有生效,所以 defStyleRes 和 theme中直接指定的优先级还不能进行判断,这个时候需要将 style.xml 文件中的下列代码进行注释:
1 <item name="defStyleAttr" >@style /def_style_attr</item>
或者将自定义 View 中调用第三个构造函数的代码做如下修改:
1 2 this (context, attrs, defStyleAttr,0 );
修改之后运行能得到如下输出: 可以看到当 defStyleAttr 不存在后 defStyleRes 开始生效,并且优先级要高于theme中直接指定。 所以最终结论如下: > XML 中直接声明相关属性值 > XML 中 引入 style > defStyleAttr > defStyleRes > theme中直接指定 > 仅在 defStyleAttr 为0或在主题中找不到时,defStyleRes 生效。
参考资料
深度解析View构造函数中的参数defStyleAttr
Android View 四个构造函数详解
深入理解Android 自定义attr Style styleable以及其应用