外层是一个圆角矩形,内层则是一个小圆形,如果细细观看,会发现外层还是有一层灰色的边框的,至少滑动的动画,我们可以考虑Scroller或者其他的动画效果。
这里我们使用了自定义属性,所以要在values目录下建立一个attrs.xml文件,
开启时背景的颜色
关闭时背景的颜色
圆形的颜色
动画执行的时间
默认宽度
默认高度
自定义一个控件这里我们就叫ButtonView,可以继承于Button也可以继承于View,这里我们就继承自View,正好复习一个看看Android内部如何实现的单击事件,看一下其原理。
重写第构造函数,这里重写构造函数我习惯重写第三个构造函数,但是遇到一些问题,记录一下,避免大家犯同样的错误。
例如
public ButtonView(Context context) {
this(context,null);
}
public ButtonView(Context context, AttributeSet attrs) {
this(context, attrs,-1);
//初始化的一些代码
}
public ButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
这么写本身是没问题,运行起来效果也是没问题的,但是当我们使用findViewById()的话获取到的则是null,其实这里只需要把-1改成0即可,至于为什么,大家可以参考一下Android的源代码。这里我就直接重写第二个构造函数了。
public ButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.buttonView);//加载自定义的属性
offBackground=a.getColor(R.styleable.buttonView_offBackground, Color.parseColor("#F6F6F6"));//获取关闭的颜色并设置默认值
onBackground=a.getColor(R.styleable.buttonView_onBackground, Color.parseColor("#38DA4E"));//获取开启的颜色并设置默认值
time=a.getInteger(R.styleable.buttonView_time, 600);//时间这里代表的是毫秒数
circleColor=a.getColor(R.styleable.buttonView_circleColor, Color.parseColor("#D7D7D7"));//圆形颜色
layout_height=a.getDimension(R.styleable.buttonView_layout_height, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics()));//默认高度
layout_width=a.getDimension(R.styleable.buttonView_layout_width, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, context.getResources().getDisplayMetrics()));//默认宽度
mPaint=new Paint();//对画笔进行一些初始化
mConfig=ViewConfiguration.get(context);//ViewConfiguration内有一个Android内部的初始值,比如多长时间是单击时间,多长时间是长按时间。
}
我们现在需要重写onMeasure方法,测量一下其宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode= MeasureSpec.getMode(heightMeasureSpec);
int heightSize= MeasureSpec.getSize(heightMeasureSpec);
int widthMode= MeasureSpec.getMode(widthMeasureSpec);
int widthSize= MeasureSpec.getSize(widthMeasureSpec);
int measureView_width = measureView(widthMode, widthSize, layout_width);
int measureView_height= measureView(heightMode, heightSize, layout_height);
//计算最大移动位置
max_move=(int) (layout_width-RADIO);
//计算最小移动位置
min_move=(int)(circleX+layout_height/2-4);
setMeasuredDimension(measureView_width, measureView_height);
}
private int measureView(int mode,int size,float defaultSize){
switch (mode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
if(size>defaultSize){
size=(int) Math.ceil(defaultSize);
}
return MeasureSpec.makeMeasureSpec(size,MeasureSpec.EXACTLY);
case MeasureSpec.EXACTLY:
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
return -1;
}
int min_move=0;最小移动的位置
int max_move=0;最大移动的位置
int current_move=min_move;当前移动的位置
int currentColor=offBackground;当前渐变的颜色
static final int RADIO=30;默认矩形圆角大小
int circleX=0;圆形X轴
这里要说一下MeasureSpec内部有三个值
而makeMeasureSpec()根据提供的大小值和模式创建一个测量值
setMeasuredDimension需要的就是两个测量值作为参数。
重写onDraw方法进行绘制内容
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
mPaint.reset();//重置画笔
mPaint.setAntiAlias(true);//设置抗锯齿
mPaint.setColor(currentColor);//设置画笔颜色
mPaint.setDither(true);//设置防抖动
canvas.drawRoundRect(new RectF(0, 0, layout_width, layout_height), RADIO, RADIO, mPaint);//绘制圆角矩形
mPaint.setStyle(Style.STROKE);//绘制边框
mPaint.setColor(Color.parseColor("#DADADA"));//设置边框的颜色
canvas.drawRoundRect(new RectF(0, 0, layout_width, layout_height), RADIO, RADIO, mPaint);//绘制矩形
mPaint.setStyle(Style.FILL);//设置填充效果
mPaint.setColor(circleColor);//设置圆形颜色
canvas.drawCircle(current_move, layout_height/2, layout_height/2-4, mPaint);//绘制圆
}
这样基本上就已经成型了,下面我们品位一下Android的单击事件如何形成的。
float x=0;
float y=0;
boolean isClick=false;
Handler mHandler=new Handler();
ClickRunAble clickRunable=new ClickRunAble();
class ClickRunAble implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
isClick=true;
}
}
private void isMove(float x,float y,int touchSlop){
if(Math.abs(this.x-x)>touchSlop||Math.abs(this.y-y)>touchSlop){
isClick=false;
}
}
上面代码执行进行准备工作,手按下然后松开即单击事件,我们重写onTouchEvent(MotionEvent event)
按下时的动作
x=event.getX();//获取按下的X轴
y=event.getY();//获取按下的Y轴
isClick=false;//默认不是单击
mHandler.postDelayed(clickRunable, mConfig.getScaledTouchSlop());//指定多长时间后 执行ClickRunAble内部代码,其实很简单就把把isClick设置成true
移动时的动作
float x=event.getX();
float y=event.getY();
isMove(x, y, mConfig.getScaledTouchSlop());如果移动距离大于ScaledTouchSlop则不是单击事件。
松开时
mHandler.removeCallbacks(clickRunable);
if(isClick){
//单击事件所要执行的代码。。。
}
即可。
这里动画效果采用Scoller明显不是很好,我们需要不变变化两个值:颜色和位置
这里我们采用属性动画
//定义我们要操作的两个属性
private Property mColor=new Property(Integer.class,"currentColor") {
@Override
public Integer get(ButtonView object) {
return object.currentColor;
}
@Override
public void set(ButtonView object, Integer value) {
// TODO Auto-generated method stub
object.currentColor=value;
invalidate();
}
};
private Property mMove=new Property(Integer.class,"current_move") {
@Override
public Integer get(ButtonView object) {
// TODO Auto-generated method stub
return object.current_move;
}
@Override
public void set(ButtonView object, Integer value) {
// TODO Auto-generated method stub
object.current_move=value;
}
};
//开启动画
public void startAnimator(int start,int end,int startColor,int endColor,int duration){
set=new AnimatorSet();
set.playTogether(ObjectAnimator.ofInt(this,mMove, start,end),ObjectAnimator.ofObject(this, mColor, new ArgbEvaluator(),startColor,endColor));
set.setDuration(duration);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// TODO Auto-generated method stub
isAnimatorStart=true;
}
@Override
public void onAnimationEnd(Animator animation) {
isAnimatorStart=false;
if(changeListener!=null){
changeListener.onChange(isOn);//当动画完毕后,我们希望开启关闭都能做一些时间,此函数进行回调
}
}
});
set.setInterpolator(new DecelerateInterpolator());
set.start();
}
上述对关键代码进行了梳理,具体请看源码。
源码
另外这里使用了属性动画,在Android3.0之前是没有效果的,这里可以使用nineoldandroids.jar兼容3.0之前的版本。
此Dome还有一些地方可以优化。