自己之前一直很懒写博客,今天想想还是写些什么吧。于是写了个自定义的switchButton,实现了点击一下具有动画滑动的切换效果,并且可以手动拖动滑块靠近两端后自己吸附到打开或者关闭位子。这个demo只是实现了功能,还有些细节需要考虑的,请大家轻点拍砖,给我留些继续写下去的动力。
效果图:
private void init(Context context,AttributeSet attrs){
if(attrs != null){
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.SwitchButton);
onColor = ta.getColor(R.styleable.SwitchButton_onColor,DEF_ONCOLOR);
offColor = ta.getColor(R.styleable.SwitchButton_offColor,DEF_OFFCOLOR);
thumbColor = ta.getColor(R.styleable.SwitchButton_thumbColor,DEF_THUMBCOLOR);
String temOnText = ta.getString(R.styleable.SwitchButton_onText);
onText = temOnText == null ? ON_TEXT:temOnText;
temOnText = ta.getString(R.styleable.SwitchButton_offText);
offText = temOnText == null ? OFF_TEXT:temOnText;
radio = ta.getDimensionPixelSize(R.styleable.SwitchButton_radio,DEF_RADIO);
if(radio > DEF_RADIO){
radio = DEF_RADIO;
}
ta.recycle();
}
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
thumb_x = getPaddingLeft();
thumb_y = getPaddingTop();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(5);
mTextPaint = new Paint();
mTextPaint.setTextSize(textSize);
mTextPaint.setColor(Color.BLACK);
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
textHOffset = - (fm.ascent + fm.descent)/2;
}
这个init函数在构造函数里被调用,获取自定义参数开/关的时的颜色,开/关所对应文字。thumb_x为thumb开始绘画的横坐标。
textHOffset偏移量让文字正好写在空间中间。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
if(wMode == MeasureSpec.AT_MOST || wSize < MIN_WIDTH){
widthMeasureSpec = MeasureSpec.makeMeasureSpec(MIN_WIDTH,MeasureSpec.EXACTLY);
}
if(hMode == MeasureSpec.AT_MOST || hSize < MIN_HEIGHT){
heightMeasureSpec = MeasureSpec.makeMeasureSpec(MIN_HEIGHT,MeasureSpec.EXACTLY);
}
setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);
}
在测量时主要是要考虑wrap_content,给了一个自己设的最小宽度,长度。
public boolean onTouchEvent(MotionEvent event) {
float currentTouchX = event.getX();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if(!isTouchInThumb(event)){
return false;
}
if(lastTouchX == -1){
lastTouchX = currentTouchX;
}
break;
case MotionEvent.ACTION_MOVE:
if(Math.abs(lastTouchX - currentTouchX) > touchSlop){
thumb_x += currentTouchX - lastTouchX;
lastTouchX = currentTouchX;
if(thumb_x > getWidth()-thumb_width - getPaddingRight()){
thumb_x = getWidth()-thumb_width;
}else if(thumb_x < getPaddingLeft()){
thumb_x = getPaddingLeft();
}
thumb_state = STATE_DRAGGING;
postInvalidate();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
lastTouchX = -1;
if(thumb_state == STATE_DRAGGING){
if(thumb_x -getPaddingLeft() + thumb_width/2 < tap_thresoul*(getWidth()-getPaddingLeft() - getPaddingRight())){
thumb_state = STATE_TO_OFF;
}else{
thumb_state = STATE_TO_ON;
}
}else if(thumb_state == STATE_ON){
thumb_state = STATE_TO_OFF;
}else if(thumb_state == STATE_OFF){
thumb_state = STATE_TO_ON;
}
}
重写事件函数,在down事件中判断点击点是否在滑块上,如果不是就返回false不对后续事件进行处理。在MOVE事件中不断对滑块的绘画起点进行更新,同时还进行边界条件的判断。
在up事件中,分两种情况。一种是拖动过,根据靠近哪端设置对应状态。另一种是点击一下滑块,会切换为对立的状态。
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
canvas.save();
canvas.translate(0,height/2);
mPaint.setStyle(Paint.Style.FILL);//关的部分
mPaint.setColor(offColor);
bgRect.set(paddingLeft+ thumb_size_ex,-(height/2 - thumb_size_ex),width-paddingRight-thumb_size_ex,height/2 - thumb_size_ex);
canvas.drawRoundRect(bgRect,radio,radio,mPaint);
mPaint.setColor(onColor); //开的部分
bgRect.set(paddingLeft + thumb_size_ex,-(height/2 - thumb_size_ex),thumb_x+thumb_width/2,height/2 - thumb_size_ex);
canvas.drawRoundRect(bgRect,radio,radio,mPaint);
mTextPaint.setColor(Color.BLACK);
mTextPaint.setTextAlign(Paint.Align.RIGHT); //关的字
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
canvas.drawText(offText,width-paddingRight-thumb_size_ex-radio,textHOffset,mTextPaint);
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTextAlign(Paint.Align.LEFT); //开的字
canvas.drawText(onText,paddingLeft + radio,textHOffset,mTextPaint);
if(thumb_state == STATE_TO_OFF){
thumb_x -= MOVE_STEP;
if(thumb_x < getPaddingLeft() ){
thumb_x = getPaddingLeft();
thumb_state = STATE_OFF;
changeOnOff(thumb_state);
}
postInvalidate();
}else if(thumb_state == STATE_TO_ON){
thumb_x += MOVE_STEP;
if(thumb_x > getWidth() - getPaddingRight() - thumb_width){
thumb_x = getWidth() - getPaddingRight() - thumb_width;
thumb_state = STATE_ON;
changeOnOff(thumb_state);
}
postInvalidate();
}
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(thumbColor); //thumb
bgRect.set(thumb_x,-height/2,thumb_x+thumb_width,height/2);
canvas.drawRoundRect(bgRect,10,10,mPaint);
mPaint.setColor((thumb_state == STATE_ON || thumb_state == STATE_TO_ON) ? onColor:offColor);
canvas.drawLine(thumb_x + thumb_width/2 - 10 ,-(height/2 - thumb_size_ex),thumb_x + thumb_width/2 - 10,height/2 + thumb_size_ex,mPaint);
canvas.drawLine(thumb_x + thumb_width/2 ,-(height/2 - thumb_size_ex),thumb_x + thumb_width/2,height/2 + thumb_size_ex,mPaint);
canvas.drawLine(thumb_x + thumb_width/2 + 10 ,-(height/2 - thumb_size_ex),thumb_x + thumb_width/2 + 10,height/2 + thumb_size_ex,mPaint);
canvas.restore();
}
postInvalidate();
break;
default:
break;
}
return true;
}
这里的实现方式是先画关闭的图形,再在上层画出打开的图形,再写字,最后是滑块。注意左右滑动动画就是通过里面
if(thumb_state == STATE_TO_OFF) 和 else if(thumb_state == STATE_TO_ON) 来判断,然后每次增加或减少MOVE_STEP给thumb_x,并在最后通过
postInvalidate()来请求绘制。如果已经滑动到位就不再调用postInvalidate()。