常见构造方式:
在Android 中要使用一个view,通常会有两种方式,1、xml中 2、通过构造函数new出一个指定的view对象。
/**
* 关于view构造参数的详解
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout container = (LinearLayout) findViewById(R.id.activity_main);
/**
* 一个参数的构造函数
*/
Button buttonOne = new Button(this);
buttonOne.setText("(Context)");
/**
* 三个参数的构造函数
*/
Button buttonThree = new Button(this, null, 0);
buttonThree.setText("(Context,AttributeSet,0)");
container.addView(buttonOne);
container.addView(buttonThree);
}
}
显然使用三个构造函数生成的button,样式不正确而且无法完成点击
View的构造函数:
通用构造函数展示如下:
public View(Context context);
public View(Context context, AttributeSet attrs);
public View(Context context, AttributeSet attrs, int defStyle);
关于button的构造函数展示如下:
public class Button extends TextView {
public Button(Context context) {
this(context, null);
}
public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
public Button(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
Button 继承自 TextView,显然由构造函数我们可知,二者的区分主要来自com.android.internal.R.attr.buttonStyle这第三个参数。
View构造方法中的第三个参数:
- 用来给View提供一个基本的style,如果我们没有对view设置某些属性,就使用这个style的属性。
通过三个参数的构造函数,进入TextView中查看构造方法,当中有一句关键代码如下,:
TypedArray a = theme.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
obtainStyledAttributes方法介绍:
public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
- set:在XML中明确写出的属性集合。(比如:android:layout_width 等)
- attrs:需要我们在上面的set集合中查询哪些内容,如果是自定义view,一般我们会把自定义的属性写在declare-styleable中,代表我们想要查询这些自定义属性。
- defStyleAttr:这是一个定义在attrs.xml文件中的attribute 这个值起作用需要两个条件:1、值不为0 2、在Theme中出现过(出现即可)
- defStyleRes:这是在styles.xml文件中定义的一个style。只有当defStyleAttr没有起作用,才会使用到这个值。
显然,一个属性最终的取值,是有一个顺序的问题,这个顺序的优先级从高到低依次是:
1、直接中在XML文件中定义。
2、在XML文件中通过style这个属性定义。
3、通过defStyleAttr定义。
4、通过defStyleRes定义。
5、直接在当前工程的theme主题下定义。
关于com.android.internal.R.attr.buttonStyle 这个位于,frameworks\base\core\res\res\values\attrs.xml 中:
<attr name="buttonStyle" format="reference" />
只是定义了一个引用,在theme.xml文件下,有这样一个style:
<style name="Theme">
...
<item name="buttonStyle">@android:style/Widget.Button</item>
...
</style>
在这里使用到了buttonStyle属性,它指向另外一个style,这个style在styles.xml文件下:
<style name="Widget.Button">
<item name="android:background">@android:drawable/btn_default</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>
<item name="android:textColor">@android:color/primary_text_light</item>
<item name="android:gravity">center_vertical|center_horizontal</item>
</style>
这些属性都是用来配置Button的,如果在XML文件中没有给Button配置背景、内容的位置等属性们就会默认的使用这里的属性。当前这是在使用了defStyleAttr的情况下才会出现的。
上面的themes.xml中的那个style的名称为Theme,而在我们自己的工程中,在配置menifest文件的时候,给application或者activity设置的主题android:theme一般都是这个style的子类,所以也就这样使用到了defStyleAttr定义的属性了
还有一个defStyleRes参数,我们可以发现在TextView、ImageView等控件中,这个值传的都是0,也就是不使用它。它的作用就像是一个替补,当defStyleAttr不起作用的时候它就上场,因为它也是一个style,这个参数是怎么起作用的在下面的实例中有提到。
public class DefineView extends View {
static final String LOG_TAG = "DefineView";
public DefineView(Context context) {
this(context, null);
}
public DefineView(Context context, AttributeSet attrs) {
/**
* 在style中,theme style中建立对应的attrs和style之间的对应关系
*/
this(context, attrs, R.attr.defineViewStyle);
}
public DefineView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
/**
* 打印各个属性的值
*/
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefineView, defStyleAttr, 0);
Log.d(LOG_TAG, "attr1 => " + array.getString(R.styleable.DefineView_attr1));
Log.d(LOG_TAG, "attr2 => " + array.getString(R.styleable.DefineView_attr2));
Log.d(LOG_TAG, "attr3 => " + array.getString(R.styleable.DefineView_attr3));
Log.d(LOG_TAG, "attr4 => " + array.getString(R.styleable.DefineView_attr4));
Log.d(LOG_TAG, "attr5 => " + array.getString(R.styleable.DefineView_attr5));
Log.d(LOG_TAG, "attr6 => " + array.getString(R.styleable.DefineView_attr6));
Log.d(LOG_TAG, "attr6 => " + array.getString(R.styleable.DefineView_attr7));
}
}
<com.example.leiiiooo92.defineview.DefineView
style="@style/xml_style"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:attr1="attr1 from xml"
app:attr2="attr2 from xml" />
<resources>
<!--定义自定义view的属性-->
<declare-styleable name="DefineView">
<attr name="attr1" format="string" />
<attr name="attr2" format="string" />
<attr name="attr3" format="string" />
<attr name="attr4" format="string" />
<attr name="attr5" format="string" />
<attr name="attr6" format="string" />
<attr name="attr7" format="string" />
</declare-styleable>
<attr name="defineViewStyle" format="reference" />
</resources>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<!--绑定attrs和style中对应项的关系-->
<item name="defineViewStyle">@style/define_view_style</item>
</style>
<!-- 首先定义我们的defStyleAttr属性(在本项目中是defineViewStyle属性)需要用到的style(位于styles.xml文件中) -->
<style name="define_view_style">
<item name="attr3">attr3 from define_view_style</item>
<item name="attr4">attr4 from define_view_style</item>
</style>
<!-- 定义一个在xml布局中需要使用到的style -->
<style name="xml_style">
<item name="attr2">attr2 from xml_style</item>
<item name="attr3">attr3 from xml_style</item>
</style>
</resources>
分析:
attr1只在xml布局文件中设置,所以值为attr1 from xml。
attr2在xml布局文件和xml style中都设置了,取值为布局文件中设置的值,所以为attr2 from xml。
attr3没有在xml布局文件中设置,但是在xml style和defStyleAttr定义的style中设置了,取xml style中的值,所以值为attr3 from xml_style。
attr4只在defStyleAttr定义的style中设置了,所以值为attr4 from custom_view_style。
下面测试一下defStyleRes这个参数,它是一个style
<style name="define_view_res_style">
<item name="attr4">attr4 from define_view_res_style</item>
<item name="attr5">attr5 from define_view_res_style</item>
</style>
当defStyleAttr这个参数定义为0(即不使用这个参数),或者是在theme中找不到defStyleAttr这个属性时(即使在theme中的配置是这样的:<item name="defStyleAttr">@null</item>,也代表找到了defStyleAttr属性,defStyleRes参数也不会生效),defStyleRes参数才会生效。
修改对应的获取自定义属性语句:
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefineView, 0, R.style.define_view_res_style);
由于defStyleAttr已经失效,所以attr4和attr5都是从define_view_res_style中获取到的值。
在主题中添加指定的属性值:
<item name="attr5">attr5 from AppTheme</item>
<item name="attr6">attr6 from AppTheme</item>
attr5在define_view_res_style和theme下都定义了,取define-res-style下的值,所以为attr5 from define_view_res_style。
attr6只在theme下定义了,所以取值为attr6 from AppTheme。
当未设置def-res-style时,主题中的属性是否生效:
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefineView, defStyle, 0);
总结:
View中的属性有多处地方可以设置值,这个优先级是:
1、直接在XML布局文件中设置的值优先级最高,如果这里设置了值,就不会去取其他地方的值了。
2、XML布局文件中有一个叫“style”的属性,它指向一个style,在这个style中设置的属性值优先级次之。
3、如果上面两个地方都没有设置值,那么就会根据View带三个参数的构造方法中的第三个参数attribute指向的style设置值,前提是这个attribute的值不为0。
4、如果上面的attribute设置为0了,我们就根据obtainStyledAttributes()方法中的最后一个参数指向的style来设置值。
5、如果仍然没有设置到值,就会用theme中直接设置的属性值,而不会去管第3步和第4步中是否设置了值。
必须要注意:要想让View构造方法的第三个参数生效,必须让它出现在我们自己的Application或者Activity的android:theme所指向的style中。设置Activity的theme一样可以。