控件是android开发过程中必不可少的部分,官方本身也给我们提供了许多的控件以供我们使用。但是有的时候已有控件并不能满足我们的需求,所以这个时候就需要我们自己来自定义控件。如果我们要学习自定义控件的话,我们需要去学习view的绘制过程这方面的知识,如果你需要为你的控件加上动画的话,你还需要学习android动画方面的知识,在这个过程中你可能还要学习一下android分发机制。有了这些基本的知识后,你就可以来自定义控件了。
下面我们就来实现一个简单的自定义控件CircleList,效果如下
上面控件主要的功能就是点击中间按钮,弹出几个子按钮,再次点击隐藏子按钮,在弹出和隐藏的过程中为子按钮添加旋转动画。
那么现在我们就来实现这个控件。
1.首先分析一下这个控件,它由数个CircleIamgeView组成(CircleImageView控件在网上可以下载,在这里不分析它的实现方式),其实它就好像是一个viewgroup,包含了数个circleImageView子控件,所以这个控件它就继承于viewgroup。
public class CircleList extends ViewGroup
{
//实现它的多个构造函数
public CircleList(Context context)
{
super(context);
}
public CircleList(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public CircleList(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
public CircleList(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
//重写它的layout方法
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3)
{
}
}
上面是继承viewgroup后需要我们重写的一些方法,可以看到,除了四个构造函数外,还有onLayout这个方法需要我们来实现。
如果你了解view的绘制过程你应该知道,第一步我们需要对这个控件进行测量,也就是Measure.。
1.Measure
在自定义控件的时候,我们需要重写onMeasure方法来对控件进行一个测量。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//从参数中得到高度和宽度的测量规格
//获取宽度和高度的测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获取宽度和高度的大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = widthSize;
int height = heightSize;
Log.e("init","ccc");
switch (widthMode)
{
case MeasureSpec.UNSPECIFIED:
width = widthSize;
break;
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
//设置一个合适的值作为默认大小
width = mSize;
break;
}
switch (heightMode)
{
case MeasureSpec.UNSPECIFIED:
height = heightSize;
break;
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = mSize;
break;
}
mWidth=width;
mHeight=height;
//设置测量后该控件的大小
setMeasuredDimension(width,height);
Log.e("draw",width+","+height+"");
//因为我们需要一个正方形的布局,所以对高度和宽度进行判断,选择小的作为布局的依据
MinSize = width > height ? height : width;
this.pading = MinSize/10;
//测量子view
measureChildren(width,height);
}
由上面代码可以看的,onMeasure这个方法中有widthMeasureSpec,heightMeasureSpec这两个参数,这两个int型参数就是你用来测量当前view的依据。
MeasureSpec参数是一个32位的Int类型数据。最高的两位代表了SpecMode(测量模式),后面的低30位代表了SpecSize(规格大小)。SpecMode有三个值,分别是UNSPECIFIED、EXACTLY、AT_MOST。
UNSPECIFIED:不指定测量模式,子视图可以是任意尺寸。
EXACTLY:精确测量模式。当设置具体数值或match_parent时生效。
AT_MOST:最大值模式,当设置为wrap_content时生效。
所以我们通过MeasureSpec.getMode与MeasureSpec.getSize这两个方法将宽与高的测量模式和规格大小提取出来。对测量模式进行一个判断。如果是AT_MOST(也就是设置了wrap_content)模式的话,则设置一个合适的大小,防止控件不显示。如果为其他两种模式的话就将得到的规格大小作为最终测量的结果。调用setMeasuredDimension(width,height)方法设置好最终了控件大小。因为当前控件继承的是viewgroup所以我们还需要将当前view测量好的规格大小,通过 measureChildren(width,height)传递个给子view,让子view也进行一个测量。
在子view测量之前,你可能会有一个疑问,就是我们并没有给这个viewGroup添加子view啊,其实我们在构造函数中需要进行一个初始化,为当前控件添加它的子view。
private void init(Context context,AttributeSet attr)
{
//得到xml中设置的自定义的属性(具体用法,下面会讲)
TypedArray typedArray = context.obtainStyledAttributes(attr,R.styleable.CircleList);
//按钮的总个数
this.mNum = typedArray.getInt(R.styleable.CircleList_button_num,5);
//是否展开四周的的按钮
this.Isshow = typedArray.getBoolean(R.styleable.CircleList_is_show,false);
//初始化所有的按钮
this.mButtons = new CircleImageView[mNum];
//初始化坐标点集合,分别代表按钮当前的位置坐标、按钮隐藏时所在坐标、按钮显示时所在坐标。
this.mPoints = new Point[mNum];
this.mHidePoints = new Point[mNum];
this.mShowPoints = new Point[mNum];
//初始化一个Image数组,用于给按钮添加背景,如果不自己设置背景的话则用下面的Image作为背景,也可以自己设置
int[] buttonImage=new int[]{R.drawable.img1,R.drawable.img2,R.drawable.img3,R.drawable</