更多内容:
(一)自定义View的分类点击打开链接
(二)自定义View的构造方法及自定义属性
(三)自定义View的常用方法(测量、绘制、位置)
(四)自定义View的具体实现
(五)事件分发机制
一、自定义View的构造方法
无论是继承自View还是ViewGroup的自定义控件,编译器都会提示我们
Implicit super constructor ViewGroup() is undefined for default constructor. Must define an explicit constructor
意思是基类没有无参的构造方法,必须定义一个带参的构造方法。
我们可以重载下面三个构造方法(API21已经有四个参数的构造方法)。
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
首先解决两个问题
一、我们在构造方法中具体做什么一般做一些初始化工作,获取自定义属性、获取子View等等,以后的demo中会具体讲解
二、如何选择构造方法
关于三个方法的选择,源码的注释里已经写得非常清楚了
/**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
*/
public View(Context context) {
……
}
带一个参数的构造
方法:
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.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @see #View(Context, AttributeSet, int)
*/
public View(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
带两个参数的构造方法:
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文件中创建一个控件时,使用此方法。这个方法传递了控件在xml文件中定义的所有属性(包括自定义属性),即attars。它所使用的的默认样式是0,这意味Context和Theme给定的attributeset是唯一用到的属性值。
/**
* Perform inflation from XML and apply a class-specific base style. 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
* <code>R.attr.buttonStyle</code> for <var>defStyle</var>; this allows
* the theme's button style to modify all of the base view attributes (in
* particular its background) as well as the Button class's attributes.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @param defStyleAttr An attribute in the current theme that contains a
* reference to a style resource to apply to this view. If 0, no
* default style will be applied.
* @see #View(Context, AttributeSet)
*/
public View(Context context, AttributeSet attrs, int defStyleAttr) {
this(context);
……
}
带三个参数的构造方法:
Perform inflation from XML and apply a class-specific base style. 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 defStyle ; this allows the theme's button style to modify all of the base view attributes (in particular its background) as well as the Button class's attributes.
在xml创建一个控件并且使用某个特定类型的基础样式时,使用此方法。
这个方法允许子类在加载时使用它们自己的基础样式。比如说,一个按钮类重载此构造方法时,将要调用基类的同类型构造方法,使用R.attr.buttonStyle作为默认样式。这使得theme的按钮样式修改所有基础控件属性(特别是其背景)以及按钮类的属性。
这里我们再看一下defStyleAttr参数的解释:
defStyleAttr: An attribute in the current theme that contains a reference to a style resource to apply to this view. If 0, no default style will be applied.
当前theme中引用自style的属性,如果是0的话,不使用任何style。
补充一句:系统不会主动的调用这个方法,需要我们显式的调用,比如:
public MyView(Context context, AttributeSet attrs) {
//显式调用三个参数的构造方法 ,将R.attr.buttonStyle作为控件的基础样式
this(context, attrs, R.attr.buttonStyle);
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
当我们显式的调用这个方法时,我们的自定义控件将使用这个defStyle作为基础的样式,但是这个defStyle所定义的属性是可以被覆盖的。比如我们在自定义控件时在xml文件中重写了defStyle的某些属性,这些属性就被覆盖了。
这是因为同时定义一个属性时,是有优先级的:xml>style>defStyle>theme
在实际运用中,为了方便,我们一般会按如下方式同时重载这三个构造方法
public MyView(Context context) {
this(context, null);// 调用两个参数的构造方法
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);//调用三个参数的构造方法
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);//调用基类构造方法
}
二、自定义View的自定义属性
对于一个自定义控件,它拥有基类所有的属性,同时也可以定义自己的属性,这就是自定义属性。
举个最简单的例子,我们现在自义定一个圆形头像,首先让它继承ImageView,这样就拥有了背景这个属性。但是作为一个圆形头像它是需要半径这个属性的,这就需要我们去自定义了。
①在res文件的values目录下,创建一个xml文件,我们将在这个文件中编写自义定属性。我创建的是attrs.xml ,名字不是固定的可以自己取。
② <declare-styleable name="控件名">
<attr name="属性名" format="属性类型" />
……
</declare-styleable>
<resources>
<declare-styleable name="CircleIcon">
<attr name="circle_radius" format="float" />
</declare-styleable>
</resources>
format选项说明及使用,这篇博客归纳的非常完整
Android自定义属性时format选项参数说明及用法
③我们怎么得到已经写好的自定义属性呢,还记得刚刚讲过的两个参数的构造方法吗,它的参数 AttributeSet attrs传入了自定义控件的所有属性,包括xml文件里申明的,也包括自定义的,因此我们在两个参数的构造方法中获取自定义属性
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs, defStyle);//调用基类构造方法
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CircleIcon);// <declare-styleable name="CircleIcon">
float radius = a.getFloat(R.styleable.CircleIcon_circle_radius,//控件名_属性名
1);
a.recycle();//一定要写
}
当我们获取了自定义属性时,就可以使用它们来构建控件了,具体的内容将在后面的文章详细讲解