构造函数
View有四个构造函数如下
public View(Context context) {}
public View(Context context, AttributeSet attrs) {}
public View(Context context, AttributeSet attrs, int defStyleAttr) {}
public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
本文先以TextView
为例理论讲解这四个构造函数如何使用,再用一个自定义View来进行实战。
一个参数的构造函数
一个参数的构造函数是在代码中创建的,例如把一个TextView
添加到Activity
布局中
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 创建TextView对象,并设置属性
TextView textView = new TextView(this);
textView.setText(R.string.app_name);
textView.setTextSize(30);
// 把TextView对象添加到布局中
setContentView(textView);
}
}
从这个例子中可以发现,使用一个参数的构造函数创建对象后,需要手动调用设置属性的方法。
两个参数的构造函数
现在假设在一个布局中声明了一个TextView
控件,代码如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="24sp" />
</RelativeLayout>
系统在解析这个XML布局的时候,会使用TextView
两个参数的构造函数,代码如下
public TextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
第二个参数AttributeSet attrs
就代表了在XML中为TextView
声明的属性集,我们可以利用它解析出声明的属性值,例如android:text
和android:textSize
。
三个参数的构造函数
我们知道,可以通过Theme
全局控制控件的样式,其中的原理就是使用三个参数的构造函数
三个参数构造函数的使用方式有点特别,一般是二个参数的构造函数中传入一个Theme
中的属性
public TextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
可以看到,TextView
两个参数的构造函数调用了三个参数的构造函数,而第三个参数使用的值就是Theme
中的com.android.internal.R.attr.textViewStyle
属性值。
如果我们想覆盖Theme
中的com.android.internal.R.attr.textViewStyle
,就需要自定义这个属性的值,代码如下
<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>
<!--定义TextView使用的属性-->
<item name="android:textViewStyle">@style/MyTextViewStyle</item>
</style>
<!--自定义TextView的颜色-->
<style name="MyTextViewStyle" parent="Widget.AppCompat.TextView">
<item name="android:textColor">@color/colorAccent</item>
</style>
四个参数的构造函数
Theme
是全局控制样式的,但是时候我们只想为某几个TextView
单独定义样式,那就得使用四个参数的构造函数。
四个参数构造函数的使用方式,一般是在三个参数的构造函数中调用,并传入自定义Style
。
首先,在styles.xml
中声明一个TextView
使用的Style
<style name="CustomTextViewStyle" parent="Widget.AppCompat.TextView" >
<item name="android:textColor">@color/colorPrimaryDark</item>
</style>
这个Style
只是简单定义了TextView
的文本颜色。
然后自定义一个继承自TextView
的控件
public class MyTextView extends TextView {
public MyTextView(Context context) {
this(context, null);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
// 注意,这里的第三个参数为0
this(context, attrs, 0);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, R.style.CustomTextViewStyle);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
在两个参数的构造函数中,调用了三个参数的构造函数,但是传入的第三个参数的值为0。至于是什么原因,后面会讲到。
在三个参数的构造函数中,调用四个参数的构造函数,第四个参数需要传入自定义的Style
。
属性值的覆盖规则
既然有这么多地方能控制属性值,那么就有个有限顺序。其实可以从四个参数的obtainStyledAttributes()
方法中看到这个规则
public final TypedArray obtainStyledAttributes(
AttributeSet set, int[] attrs, int defStyleAttr,
int defStyleRes) {}
第一个参数AttributeSet set
指的是XML中声明的属性集。
第三个参数int defStyleAttr
指的是Theme
中的控制控件的属性。
第四个参数int defStyleRes
指的是自定义的Style
。
那么最简单属性值获取的优先规则就是第一个参数,第三个参数,第四个参数。
如果在XML给控件使用style
属性呢?它的优先级是介于第一个参数和第三个参数之间。
那么最终的优先规则如下
- XML中属性
- XML中style属性
- Theme中属性
- 自定义Style
自定义View
现在,通过一个自定义View演示四个构造函数如何使用。
自定义View名字叫SimpleView
,在这个控件中,只简单绘制一个圆,并且它有一个自定义属性,可以控制圆的颜色。
首先声明SimpleView
使用的自定义属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--SimpleView的自定义属性,控制圆的颜色-->
<declare-styleable name="SimpleView">
<attr name="circleColor" format="color|reference" />
</declare-styleable>
</resources>
然后,在两个参数的构造函数中解析这个属性颜色值,并使用这个颜色值绘制圆
public class SimpleView extends View {
Paint mPaint = new Paint();
int mColor = Color.BLACK;
int mCenterX, mCenterY;
int mRadius;
public SimpleView(Context context) {
this(context, null);
}
public SimpleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// 获取XML中声明的属性集,并解析属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleView);
for (int i = 0; i < a.getIndexCount(); i++) {
int attrIndex = a.getIndex(i);
if (attrIndex == R.styleable.SimpleView_circleColor) {
mColor = a.getColor(attrIndex, Color.BLACK);
}
}
a.recycle();
mPaint.setColor(mColor);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mCenterX = w / 2;
mCenterY = h / 2;
mRadius = w < h ? w / 4 : h / 4;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
}
}
SimpleView
一个参数的构造函数调用了两个参数的构造函数,在两个参数的构造函数中解析了自定义的属性circleColor
。
那么现在,在XML中使用SimpleView
,然后设置circleColor
属性,就可以绘制你想要的颜色的圆
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.umx.viewconstructortest.SimpleView
app:circleColor="@color/colorAccent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
通过Theme控制样式
现在,我们想通过Theme
的属性控制SimpleView
样式。按照之前所说,那就需要一个属性并在Theme
中声明,然后通过三个参数的构造函数来完成Theme
的控制。
首先定义Theme
使用的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--SimpleView的自定义属性-->
<declare-styleable name="SimpleView">
<attr name="circleColor" format="color|reference" />
</declare-styleable>
<!--在Theme中控制SimpleView样式的属性-->
<attr name="SimpleViewStyle" format="reference" />
</resources>
SimpleViewStyle
就是SimpleView
要使用的Theme
的属性。
现在,我们在Theme
中加入这个属性
<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>
<!--SimpleViewStyle控制SimpleView样式-->
<item name="SimpleViewStyle">@style/MySimpleViewStyle</item>
</style>
<style name="MySimpleViewStyle">
<!--统一控制SimpleView使用的颜色为黑色-->
<item name="circleColor">#000000</item>
</style>
</resources>
这一切准备就绪后,就来实现三个参数的构造函数
public class SimpleView extends View {
public SimpleView(Context context) {
this(context, null);
}
public SimpleView(Context context, @Nullable AttributeSet attrs) {
// 调用三个参数的构造函数,传入的第三个参数为Theme中声明的SimpleViewStyle属性
this(context, attrs, R.attr.SimpleViewStyle);
}
public SimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 注意,这里使用的obtainStyledAttributes方法有四个参数
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleView, defStyleAttr, 0);
for (int i = 0; i < a.getIndexCount(); i++) {
int attrIndex = a.getIndex(i);
if (attrIndex == R.styleable.SimpleView_circleColor) {
mColor = a.getColor(attrIndex, Color.BLACK);
}
}
a.recycle();
mPaint.setColor(mColor);
}
}
在两个参数的构造函数中调用了三个参数的构造函数,并且第三个参数传入的就是刚才自定义且在Theme
中声明的属性。然后在三个参数的构造函数中,为了使用刚刚传入的Theme
属性,必须使用有四个参数的obtainStyledAttributes()
方法,这里一定要注意。
然后在三个参数的构造函数中完成了自定义属性的解析,取代两个参数的构造函数的工作。
自定义Style控制样式
如果你想自定义一个拥有特殊样式的SimpleView
,按照前面的分析,你需要使用四个参数的构造函数,并且需要继承SimpleView
。
首先,需要在SimpleView
中加入四个参数的构造函数
public class SimpleView extends View {
public SimpleView(Context context) {
this(context, null);
}
public SimpleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.SimpleViewStyle);
}
public SimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public SimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
// 注意,这里也是使用四个参数的obtainStyledAttributes()方法
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleView, defStyleAttr, defStyleRes);
for (int i = 0; i < a.getIndexCount(); i++) {
int attrIndex = a.getIndex(i);
if (attrIndex == R.styleable.SimpleView_circleColor) {
mColor = a.getColor(attrIndex, Color.BLACK);
}
}
a.recycle();
mPaint.setColor(mColor);
}
}
实现四个参数的构造函数非常简单,只需要在三个参数的构造函数中调用四个参数的构造函数,然后把第四个参数传入0即可。同时我把属性的解析移到了四个参数的构造函数中。
现在,定义一个继承子SimpleView
的类CustomSimpleView
,并且传入自定义的样式
public class CustomSimpleView extends SimpleView {
public CustomSimpleView(Context context) {
this(context, null);
}
public CustomSimpleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomSimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, R.style.CustomSimpleViewStyle);
}
public CustomSimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
CustomSimpleView
的两个参数的构造函数中,调用了三个参数的构造函数,然而第三个参数传入的0,也就是说CustomSimpleView
不想使用Theme
控制它的样式。
CustomSimpleView
的三个参数的构造函数中,调用了四个参数的构造函数,并且第四个参数传入了自定义的样式R.style.CustomSimpleViewStyle
。
CustomSimpleViewStyle
自定义样式如下
<resources>
<style name="CustomSimpleViewStyle">
<item name="circleColor">#ff0000</item>
</style>
</resources>
如此一来,CusstomSimpleView
就不能通过Theme
控制样式,而使用的是自定义的Style
控制样式。
结束
平常我们可能用不到这些知识,但是在写系统控件的时候,这个就不能忽视,尤其在我们自定义系统的Theme
的时候,就显得尤为重要。